Parcourir la source

Merge remote-tracking branch 'origin/saas-api' into saas-api

yys il y a 4 heures
Parent
commit
992ebcca9a
23 fichiers modifiés avec 2704 ajouts et 208 suppressions
  1. 58 199
      fs-admin-saas/src/main/java/com/fs/lobster/controller/LobsterAdminController.java
  2. 129 0
      fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterAdminController.java
  3. 53 0
      fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterPromptController.java
  4. 54 0
      fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterSalesCorpusController.java
  5. 125 4
      fs-admin/src/main/java/com/fs/admin/controller/tenant/TenantInfoController.java
  6. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  7. 6 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterAuxiliaryMapper.java
  8. 42 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java
  9. 3 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterWorkflowInstanceMapper.java
  10. 27 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterEvolutionSuggestionService.java
  11. 11 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterInstanceStatsService.java
  12. 22 0
      fs-service/src/main/java/com/fs/company/service/workflow/IWorkflowTemplateAdminService.java
  13. 86 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterEvolutionSuggestionServiceImpl.java
  14. 48 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterInstanceStatsServiceImpl.java
  15. 162 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/WorkflowTemplateAdminServiceImpl.java
  16. 2 2
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserSignScrmServiceImpl.java
  17. 6 0
      fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java
  18. 3 0
      fs-service/src/main/resources/db/tenant-initTable-migration.sql
  19. 1837 2
      fs-service/src/main/resources/db/tenant-initTable.sql
  20. 5 0
      fs-service/src/main/resources/mapper/company/LobsterWorkflowInstanceMapper.xml
  21. 10 0
      fs-service/src/main/resources/mapper/lobster/LobsterAuxiliaryMapper.xml
  22. 10 0
      fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml
  23. 1 1
      fs-user-app/src/main/java/com/fs/app/controller/store/UserSignScrmController.java

+ 58 - 199
fs-admin-saas/src/main/java/com/fs/lobster/controller/LobsterAdminController.java

@@ -20,7 +20,7 @@ import java.util.*;
 
 /**
  * 龙虾引擎管理端 Controller(fs-admin-saas)
- * 直连 MyBatis Service / JdbcTemplate 租户库,无桥接镜像表
+ * 直连 MyBatis Service 租户库,无桥接镜像表
  */
 @RestController
 public class LobsterAdminController extends BaseController {
@@ -55,9 +55,6 @@ public class LobsterAdminController extends BaseController {
     @Autowired(required = false)
     private com.fs.company.service.workflow.LobsterWorkflowExecutor workflowExecutor;
 
-    @Autowired(required = false)
-    private org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;
-
     @Autowired(required = false)
     private com.fs.company.mapper.LobsterChatSessionMapper chatSessionMapper;
 
@@ -73,6 +70,18 @@ public class LobsterAdminController extends BaseController {
     @Autowired(required = false)
     private com.fs.company.service.workflow.channel.MessageChannelRouter messageChannelRouter;
 
+    @Autowired(required = false)
+    private com.fs.company.service.workflow.ILobsterInstanceStatsService instanceStatsService;
+
+    @Autowired(required = false)
+    private com.fs.company.service.workflow.ILobsterEvolutionSuggestionService evolutionSuggestionService;
+
+    @Autowired(required = false)
+    private com.fs.company.service.workflow.IWorkflowTemplateAdminService workflowTemplateAdminService;
+
+    @Autowired(required = false)
+    private com.fs.company.mapper.CompanyMapper companyMapper;
+
     @Autowired
     private TokenService tokenService;
 
@@ -195,113 +204,47 @@ public class LobsterAdminController extends BaseController {
 
     @GetMapping({"/workflow/lobster/generate", "/workflow/lobster/generate/list"})
     public AjaxResult lobsterGenerate(@RequestParam(required = false) Long companyId) {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
-        try {
-            String sql = "SELECT id, company_id, workflow_id, node_code, suggestion_type, reason, confidence, status, create_time " +
-                    "FROM lobster_evolution_suggestion WHERE 1=1";
-            List<Object> params = new ArrayList<>();
-            if (companyId != null) {
-                sql += " AND company_id=?";
-                params.add(companyId);
-            }
-            sql += " ORDER BY create_time DESC LIMIT 100";
-            return AjaxResult.success(jdbcTemplate.queryForList(sql, params.toArray()));
-        } catch (Exception e) {
-            return AjaxResult.success(new ArrayList<>());
+        if (evolutionSuggestionService != null) {
+            return AjaxResult.success(evolutionSuggestionService.listSuggestions(companyId));
         }
+        return AjaxResult.success(new ArrayList<>());
     }
 
     @GetMapping({"/workflow/lobster/canvas", "/workflow/lobster/canvas/list"})
     public AjaxResult lobsterCanvas(@RequestParam(required = false) Long companyId) {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
-        try {
-            String sql = "SELECT id, company_id, template_code, template_name, industry_type, status, version, " +
-                    "canvas_data, update_time, create_time FROM company_workflow_lobster WHERE del_flag=0";
-            List<Object> params = new ArrayList<>();
-            if (companyId != null) {
-                sql += " AND company_id=?";
-                params.add(companyId);
-            }
-            sql += " ORDER BY update_time DESC LIMIT 200";
-            return AjaxResult.success(jdbcTemplate.queryForList(sql, params.toArray()));
-        } catch (Exception e) {
-            return AjaxResult.success(new ArrayList<>());
+        if (workflowTemplateAdminService != null) {
+            return AjaxResult.success(workflowTemplateAdminService.listTemplates(companyId));
         }
+        return AjaxResult.success(new ArrayList<>());
     }
 
     @GetMapping({"/workflow/lobster/template", "/workflow/lobster/template/list"})
     public AjaxResult lobsterTemplate() {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
-        List<Map<String, Object>> list = jdbcTemplate.queryForList(
-            "SELECT id, template_code, template_name, industry_type, description, status, version, create_time, update_time " +
-            "FROM company_workflow_lobster WHERE del_flag=0 AND status=1 ORDER BY update_time DESC LIMIT 200");
-        return AjaxResult.success(list);
+        if (workflowTemplateAdminService != null) {
+            return AjaxResult.success(workflowTemplateAdminService.listPublishedTemplates());
+        }
+        return AjaxResult.success(new ArrayList<>());
     }
 
     /** 获取工作流节点列表(含模板信息) */
     @GetMapping("/workflow/lobster/nodes/{workflowId}")
     public AjaxResult getWorkflowNodes(@PathVariable Long workflowId) {
-        if (jdbcTemplate == null) return AjaxResult.error("DB不可用");
-        Map<String, Object> template = jdbcTemplate.queryForMap(
-            "SELECT id, template_code, template_name, industry_type, description, status " +
-            "FROM company_workflow_lobster WHERE id=? AND del_flag=0", workflowId);
-        List<Map<String, Object>> nodes = jdbcTemplate.queryForList(
-            "SELECT id, workflow_id, node_code, node_name, node_type, sort_no, " +
-            "next_node_code, message_template, condition_expr, node_config, scene_code, model_name, send_time, max_round " +
-            "FROM company_workflow_lobster_node WHERE workflow_id=? AND del_flag=0 ORDER BY sort_no", workflowId);
-        Map<String, Object> result = new HashMap<>();
-        result.put("template", template);
-        result.put("nodes", nodes);
-        return AjaxResult.success(result);
+        if (workflowTemplateAdminService != null) {
+            return AjaxResult.success(workflowTemplateAdminService.getTemplateWithNodes(workflowId));
+        }
+        return AjaxResult.error("模板服务不可用");
     }
 
     /** 保存工作流节点(先删后插) */
     @PostMapping("/workflow/lobster/nodes/save")
     public AjaxResult saveWorkflowNodes(@RequestBody Map<String, Object> body) {
-        if (jdbcTemplate == null) return AjaxResult.error("DB不可用");
-        Long workflowId = toLong(body.get("workflowId"));
-        if (workflowId == null) return AjaxResult.error("workflowId必填");
-        // 更新模板头
-        String templateName = (String) body.get("templateName");
-        String industryType = (String) body.get("industryType");
-        String description = (String) body.get("description");
-        if (templateName != null) {
-            jdbcTemplate.update(
-                "UPDATE company_workflow_lobster SET template_name=?, industry_type=?, description=?, update_time=NOW() WHERE id=?",
-                templateName, industryType, description, workflowId);
-        }
-        // 清空旧节点
-        jdbcTemplate.update("UPDATE company_workflow_lobster_node SET del_flag=1, update_time=NOW() WHERE workflow_id=?", workflowId);
-        // 插入新节点
-        @SuppressWarnings("unchecked")
-        List<Map<String, Object>> nodes = (List<Map<String, Object>>) body.get("nodes");
-        if (nodes != null) {
-            for (Map<String, Object> n : nodes) {
-                jdbcTemplate.update(
-                    "INSERT INTO company_workflow_lobster_node(workflow_id, node_code, node_name, node_type, sort_no, " +
-                    "next_node_code, message_template, condition_expr, node_config, scene_code, model_name, send_time, max_round, create_time) " +
-                    "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,NOW())",
-                    workflowId,
-                    n.getOrDefault("nodeCode", ""),
-                    n.getOrDefault("nodeName", ""),
-                    toInt(n, "nodeType", 2),
-                    toInt(n, "sortNo", 0),
-                    n.getOrDefault("nextNodeCode", null),
-                    n.getOrDefault("messageTemplate", null),
-                    n.getOrDefault("conditionExpr", null),
-                    n.getOrDefault("nodeConfig", null),
-                    n.getOrDefault("sceneCode", null),
-                    n.getOrDefault("modelName", null),
-                    n.getOrDefault("sendTime", null),
-                    toInt(n, "maxRound", 0));
-            }
+        if (workflowTemplateAdminService == null) return AjaxResult.error("模板服务不可用");
+        try {
+            workflowTemplateAdminService.saveTemplateNodes(body);
+            return AjaxResult.success("保存成功");
+        } catch (Exception e) {
+            return AjaxResult.error(e.getMessage());
         }
-        return AjaxResult.success("保存成功");
-    }
-
-    private int toInt(Map<String, Object> map, String key, int def) {
-        Object v = map.get(key);
-        return v instanceof Number ? ((Number) v).intValue() : def;
     }
 
     @GetMapping({"/workflow/lobster/instance", "/workflow/lobster/instance/list"})
@@ -312,36 +255,13 @@ public class LobsterAdminController extends BaseController {
 
     @GetMapping("/workflow/lobster/instance/stats")
     public AjaxResult lobsterInstanceStats(@RequestParam(required = false) Long companyId) {
-        Map<String, Object> stats = new HashMap<>();
-        if (jdbcTemplate != null) {
-            try {
-                String base = " FROM lobster_workflow_instance WHERE del_flag=0";
-                List<Object> params = new ArrayList<>();
-                if (companyId != null) { base += " AND company_id=?"; params.add(companyId); }
-                stats.put("running", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*)" + base + " AND status='running'", params.toArray(), Integer.class));
-                stats.put("paused", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*)" + base + " AND status='paused'", params.toArray(), Integer.class));
-                stats.put("completed", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*)" + base + " AND status='completed'", params.toArray(), Integer.class));
-                stats.put("deadLetters", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_dead_letter_queue WHERE status='pending'"
-                                + (companyId != null ? " AND company_id=?" : ""),
-                        companyId != null ? new Object[]{companyId} : new Object[]{}, Integer.class));
-                Object tokens = jdbcTemplate.queryForObject(
-                        "SELECT COALESCE(SUM(token_count),0) FROM lobster_token_consume_log WHERE DATE(create_time)=CURDATE()"
-                                + (companyId != null ? " AND company_id=?" : ""),
-                        companyId != null ? new Object[]{companyId} : new Object[]{}, Object.class);
-                stats.put("todayTokens", tokens != null ? tokens.toString() : "0");
-            } catch (Exception e) {
-                stats.put("running", 0); stats.put("paused", 0);
-                stats.put("deadLetters", 0); stats.put("todayTokens", "0");
-            }
-        } else {
-            stats.put("running", 0); stats.put("paused", 0);
-            stats.put("deadLetters", 0); stats.put("todayTokens", "0");
+        if (instanceStatsService != null) {
+            return AjaxResult.success(instanceStatsService.getStats(companyId));
         }
-        return AjaxResult.success(stats);
+        Map<String, Object> empty = new HashMap<>();
+        empty.put("running", 0); empty.put("paused", 0); empty.put("completed", 0);
+        empty.put("deadLetters", 0); empty.put("todayTokens", "0");
+        return AjaxResult.success(empty);
     }
 
     @GetMapping("/workflow/lobster/instance/{instanceId}")
@@ -364,34 +284,18 @@ public class LobsterAdminController extends BaseController {
 
     @GetMapping({"/workflow/lobster/optimization", "/workflow/lobster/optimization/list"})
     public AjaxResult lobsterOptimization(@RequestParam(required = false) Long companyId) {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
-        try {
-            if (companyId != null) {
-                return AjaxResult.success(jdbcTemplate.queryForList(
-                        "SELECT * FROM lobster_evolution_suggestion WHERE company_id=? ORDER BY create_time DESC LIMIT 200",
-                        companyId));
-            }
-            return AjaxResult.success(jdbcTemplate.queryForList(
-                    "SELECT * FROM lobster_evolution_suggestion ORDER BY create_time DESC LIMIT 200"));
-        } catch (Exception e) {
-            return AjaxResult.success(new ArrayList<>());
+        if (evolutionSuggestionService != null) {
+            return AjaxResult.success(evolutionSuggestionService.listByStatus(companyId, -1, 200));
         }
+        return AjaxResult.success(new ArrayList<>());
     }
 
     @GetMapping("/workflow/lobster/optimization/pending-audit")
     public AjaxResult lobsterOptimizationPendingAudit(@RequestParam(required = false) Long companyId) {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
-        try {
-            if (companyId != null) {
-                return AjaxResult.success(jdbcTemplate.queryForList(
-                        "SELECT * FROM lobster_evolution_suggestion WHERE company_id=? AND status=0 ORDER BY create_time DESC LIMIT 100",
-                        companyId));
-            }
-            return AjaxResult.success(jdbcTemplate.queryForList(
-                    "SELECT * FROM lobster_evolution_suggestion WHERE status=0 ORDER BY create_time DESC LIMIT 100"));
-        } catch (Exception e) {
-            return AjaxResult.success(new ArrayList<>());
+        if (evolutionSuggestionService != null) {
+            return AjaxResult.success(evolutionSuggestionService.listPendingAudit(companyId, 100));
         }
+        return AjaxResult.success(new ArrayList<>());
     }
 
     @PostMapping("/workflow/lobster/optimization/batch-audit")
@@ -409,14 +313,8 @@ public class LobsterAdminController extends BaseController {
             return AjaxResult.success(evolutionEngine.analyzeAndSuggest(companyId, workflowId));
         }
         Map<String, Object> result = new HashMap<>();
-        if (jdbcTemplate != null && companyId != null) {
-            try {
-                Integer total = jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=?", Integer.class, companyId);
-                result.put("totalSuggestions", total != null ? total : 0);
-            } catch (Exception e) {
-                result.put("totalSuggestions", 0);
-            }
+        if (evolutionSuggestionService != null && companyId != null) {
+            result.put("totalSuggestions", evolutionSuggestionService.getStats(companyId).getOrDefault("total", 0));
         } else {
             result.put("totalSuggestions", 0);
         }
@@ -425,21 +323,12 @@ public class LobsterAdminController extends BaseController {
 
     @GetMapping("/workflow/lobster/optimization/stats")
     public AjaxResult lobsterOptimizationStats(@RequestParam(required = false) Long companyId) {
+        if (evolutionSuggestionService != null && companyId != null) {
+            return AjaxResult.success(evolutionSuggestionService.getStats(companyId));
+        }
         Map<String, Object> stats = new HashMap<>();
         stats.put("total", 0); stats.put("pending", 0);
         stats.put("approved", 0); stats.put("rejected", 0);
-        if (jdbcTemplate != null && companyId != null) {
-            try {
-                stats.put("total", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=?", Integer.class, companyId));
-                stats.put("pending", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=? AND status=0", Integer.class, companyId));
-                stats.put("approved", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=? AND status=1", Integer.class, companyId));
-                stats.put("rejected", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=? AND status=2", Integer.class, companyId));
-            } catch (Exception ignored) { }
-        }
         return AjaxResult.success(stats);
     }
 
@@ -590,18 +479,6 @@ public class LobsterAdminController extends BaseController {
     public AjaxResult lobsterExecInstanceList(@RequestParam(required = false) Long companyId,
                                                @RequestParam(required = false) Long workflowId,
                                                @RequestParam(required = false) String status) {
-        if (jdbcTemplate != null) {
-            StringBuilder sql = new StringBuilder(
-                "SELECT id, company_id, workflow_id, instance_name, status, contact_id, control_mode, " +
-                "current_node_index, current_node_name, total_nodes, completed_nodes, create_time, update_time " +
-                "FROM lobster_workflow_instance WHERE del_flag=0");
-            List<Object> params = new ArrayList<>();
-            if (companyId != null) { sql.append(" AND company_id=?"); params.add(companyId); }
-            if (workflowId != null) { sql.append(" AND workflow_id=?"); params.add(workflowId); }
-            if (status != null && !status.isEmpty()) { sql.append(" AND status=?"); params.add(status); }
-            sql.append(" ORDER BY create_time DESC LIMIT 500");
-            return AjaxResult.success(jdbcTemplate.queryForList(sql.toString(), params.toArray()));
-        }
         if (workflowInstanceMapper == null) return AjaxResult.success(new ArrayList<>());
         if (companyId == null) return AjaxResult.success(new ArrayList<>());
         List<com.fs.company.domain.LobsterWorkflowInstance> list = workflowInstanceMapper.selectByCompanyId(companyId);
@@ -614,10 +491,9 @@ public class LobsterAdminController extends BaseController {
         if (workflowExecutor != null && companyId != null) {
             return AjaxResult.success(workflowExecutor.getInstanceState(companyId, instanceId));
         }
-        if (jdbcTemplate != null) {
+        if (workflowInstanceMapper != null) {
             try {
-                return AjaxResult.success(jdbcTemplate.queryForMap(
-                    "SELECT * FROM lobster_workflow_instance WHERE id=? AND del_flag=0", instanceId));
+                return AjaxResult.success(workflowInstanceMapper.selectById(instanceId));
             } catch (Exception e) {
                 return AjaxResult.error("实例不存在");
             }
@@ -631,11 +507,6 @@ public class LobsterAdminController extends BaseController {
     @GetMapping("/workflow/lobster-exec/node-logs/{instanceId}")
     public AjaxResult lobsterExecNodeLogs(@PathVariable Long instanceId,
                                            @RequestParam(required = false) Long companyId) {
-        if (jdbcTemplate != null) {
-            return AjaxResult.success(jdbcTemplate.queryForList(
-                "SELECT * FROM lobster_node_execution_log WHERE instance_id=? ORDER BY create_time DESC LIMIT 200",
-                instanceId));
-        }
         if (nodeExecutionLogMapper != null && companyId != null) {
             return AjaxResult.success(nodeExecutionLogMapper.selectByInstanceId(instanceId, companyId));
         }
@@ -853,21 +724,11 @@ public class LobsterAdminController extends BaseController {
         if (evolutionEngine != null && companyId != null) {
             return AjaxResult.success(evolutionEngine.getEvolutionMetrics(companyId));
         }
-        Map<String, Object> data = new HashMap<>();
-        if (jdbcTemplate != null && companyId != null) {
-            try {
-                data.put("totalEvolutions", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_log WHERE company_id=?", Integer.class, companyId));
-                data.put("appliedCount", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=? AND status=1", Integer.class, companyId));
-                data.put("pendingCount", jdbcTemplate.queryForObject(
-                        "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id=? AND status=0", Integer.class, companyId));
-            } catch (Exception e) {
-                data.put("totalEvolutions", 0); data.put("appliedCount", 0); data.put("pendingCount", 0);
-            }
-        } else {
-            data.put("totalEvolutions", 0); data.put("appliedCount", 0); data.put("pendingCount", 0);
+        if (evolutionSuggestionService != null && companyId != null) {
+            return AjaxResult.success(evolutionSuggestionService.getEvolutionMetrics(companyId));
         }
+        Map<String, Object> data = new HashMap<>();
+        data.put("totalEvolutions", 0); data.put("appliedCount", 0); data.put("pendingCount", 0);
         return AjaxResult.success(data);
     }
 
@@ -1144,11 +1005,9 @@ public class LobsterAdminController extends BaseController {
 
     @GetMapping("/workflow/lobster-admin/companies")
     public AjaxResult adminCompanies() {
-        if (jdbcTemplate == null) return AjaxResult.success(new ArrayList<>());
+        if (companyMapper == null) return AjaxResult.success(new ArrayList<>());
         try {
-            List<Map<String, Object>> list = jdbcTemplate.queryForList(
-                "SELECT id, company_name, domain, status FROM company_info WHERE del_flag=0 ORDER BY id");
-            return AjaxResult.success(list);
+            return AjaxResult.success(companyMapper.selectAdminCompanyList());
         } catch (Exception e) {
             return AjaxResult.success(new ArrayList<>());
         }

+ 129 - 0
fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterAdminController.java

@@ -0,0 +1,129 @@
+package com.fs.admin.controller.lobster;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.company.domain.LobsterWorkflowInstance;
+import com.fs.company.service.workflow.ILobsterBillingService;
+import com.fs.company.service.workflow.ILobsterEventAuditService;
+import com.fs.company.service.workflow.ILobsterSalesCorpusService;
+import com.fs.company.service.workflow.LobsterModelConfigService;
+import com.fs.framework.web.service.TokenService;
+import com.fs.proxy.service.AiChatQualityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 龙虾引擎管理端 Controller(fs-admin-saas)
+ * 直连 MyBatis Service / JdbcTemplate 租户库,无桥接镜像表
+ */
+@RestController
+public class LobsterAdminController extends BaseController {
+
+    @Autowired(required = false)
+    private com.fs.company.service.workflow.ILobsterInstanceStatsService instanceStatsService;
+
+    @Autowired(required = false)
+    private com.fs.company.mapper.LobsterWorkflowInstanceMapper workflowInstanceMapper;
+
+    @Autowired
+    private ILobsterSalesCorpusService salesCorpusService;
+
+    @Autowired(required = false)
+    private com.fs.company.service.workflow.ILobsterEvolutionSuggestionService evolutionSuggestionService;
+
+    @Autowired(required = false)
+    private com.fs.company.mapper.CompanyMapper companyMapper;
+
+    @GetMapping("/workflow/lobster/instance/stats")
+    public AjaxResult lobsterInstanceStats(@RequestParam(required = false) Long companyId) {
+        if (instanceStatsService != null) {
+            return AjaxResult.success(instanceStatsService.getStats(companyId));
+        }
+        Map<String, Object> empty = new HashMap<>();
+        empty.put("running", 0); empty.put("paused", 0); empty.put("completed", 0);
+        empty.put("deadLetters", 0); empty.put("todayTokens", "0");
+        return AjaxResult.success(empty);
+    }
+
+    @GetMapping({"/workflow/lobster/instance", "/workflow/lobster/instance/list"})
+    public AjaxResult lobsterInstance(@RequestParam(required = false) Long companyId,
+                                      @RequestParam(required = false) String status) {
+        return lobsterExecInstanceList(companyId, null, status);
+    }
+
+    @GetMapping({"/workflow/lobster-exec/instance", "/workflow/lobster-exec/instance/list"})
+    public AjaxResult lobsterExecInstanceList(@RequestParam(required = false) Long companyId,
+                                              @RequestParam(required = false) Long workflowId,
+                                              @RequestParam(required = false) String status) {
+        if (workflowInstanceMapper == null) return AjaxResult.success(new ArrayList<>());
+        if (companyId == null) return AjaxResult.success(new ArrayList<>());
+        List<LobsterWorkflowInstance> list = workflowInstanceMapper.selectByCompanyId(companyId);
+        return AjaxResult.success(list != null ? list : new ArrayList<>());
+    }
+
+    @GetMapping({"/workflow/lobster/sales-corpus", "/workflow/lobster/sales-corpus/list",
+            "/workflow/lobster/corpus", "/workflow/lobster/corpus/list"})
+    public AjaxResult lobsterCorpus(@RequestParam(defaultValue = "1") int page,
+                                    @RequestParam(defaultValue = "10") int size,
+                                    @RequestParam(required = false) String scenario,
+                                    @RequestParam(required = false) String status,
+                                    @RequestParam(required = false) Long companyId) {
+        return AjaxResult.success(salesCorpusService.listCorpus(page, size, companyId, scenario, status));
+    }
+
+    @GetMapping("/workflow/lobster/sales-corpus/scenarios")
+    public AjaxResult lobsterCorpusScenarios() {
+        return AjaxResult.success(salesCorpusService.getScenarios());
+    }
+
+    @GetMapping("/workflow/lobster/optimization/pending-audit")
+    public AjaxResult lobsterOptimizationPendingAudit(@RequestParam(required = false) Long companyId) {
+        if (evolutionSuggestionService != null) {
+            return AjaxResult.success(evolutionSuggestionService.listPendingAudit(companyId, 100));
+        }
+        return AjaxResult.success(new ArrayList<>());
+    }
+
+    @GetMapping("/workflow/lobster/optimization/stats")
+    public AjaxResult lobsterOptimizationStats(@RequestParam(required = false) Long companyId) {
+        if (evolutionSuggestionService != null && companyId != null) {
+            return AjaxResult.success(evolutionSuggestionService.getStats(companyId));
+        }
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("total", 0); stats.put("pending", 0);
+        stats.put("approved", 0); stats.put("rejected", 0);
+        return AjaxResult.success(stats);
+    }
+
+    @GetMapping("/workflow/lobster-admin/companies")
+    public AjaxResult adminCompanies() {
+        if (companyMapper == null) return AjaxResult.success(new ArrayList<>());
+        try {
+            return AjaxResult.success(companyMapper.selectAdminCompanyList());
+        } catch (Exception e) {
+            return AjaxResult.success(new ArrayList<>());
+        }
+    }
+
+    @GetMapping({"/workflow/lobster/dead-letter", "/workflow/lobster/dead-letter/list",
+            "/workflow/lobster/deadLetter", "/workflow/lobster/deadLetter/list"})
+    public AjaxResult lobsterDeadLetter() { return AjaxResult.success(new ArrayList<>()); }
+
+    @GetMapping("/workflow/lobster/dead-letter/stats")
+    public AjaxResult lobsterDeadLetterStats() {
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("total", 0); stats.put("pending", 0); stats.put("retried", 0);
+        return AjaxResult.success(stats);
+    }
+
+    @PostMapping("/workflow/lobster/dead-letter/retry-all")
+    public AjaxResult lobsterDeadLetterRetryAll() { return AjaxResult.success("重试已提交"); }
+}

+ 53 - 0
fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterPromptController.java

@@ -0,0 +1,53 @@
+package com.fs.admin.controller.lobster;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.LobsterSystemPrompt;
+import com.fs.company.param.LobsterPromptParam;
+import com.fs.company.service.workflow.ILobsterPromptService;
+import com.fs.framework.web.service.TokenService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 龙虾系统提示词管理Controller
+ * 表: lobster_system_prompt
+ * 页面: Prompt管理 → 增删改查 + 租户/行业筛选 + 缓存刷新
+ */
+@RestController
+@RequestMapping("/workflow/lobster/prompt")
+public class LobsterPromptController extends BaseController {
+
+    @Autowired
+    private ILobsterPromptService promptService;
+
+    @PreAuthorize("@ss.hasPermi('workflow:lobster:query')")
+    @GetMapping("/list")
+    public AjaxResult list(@RequestParam(defaultValue = "1") int page,
+                           @RequestParam(defaultValue = "10") int size,
+                           @RequestParam(required = false) String category,
+                           @RequestParam(required = false) String search) {
+        Map<String, Object> result = promptService.listPrompts(page, size, category, search);
+        return AjaxResult.success(result);
+    }
+
+    @GetMapping("/categories")
+    public AjaxResult categories() {
+        List<String> cats = promptService.getCategories();
+        return AjaxResult.success(cats);
+    }
+
+    @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
+    @PostMapping("/refresh-cache")
+    public AjaxResult refreshCache() {
+        promptService.refreshCache();
+        return AjaxResult.success("缓存已刷新");
+    }
+}

+ 54 - 0
fs-admin/src/main/java/com/fs/admin/controller/lobster/LobsterSalesCorpusController.java

@@ -0,0 +1,54 @@
+package com.fs.admin.controller.lobster;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.company.service.workflow.ILobsterSalesCorpusService;
+import com.fs.company.service.workflow.learning.SalesCorpusAnalyzer;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 龙虾销冠语料管理Controller
+ *
+ * 表: lobster_sales_corpus
+ * 页面: 销冠语料 → 录入/批量导入/AI分析/话术库查询
+ *
+ * 核心价值: 租户上传销冠/金牌客服聊天话术 → AI分析提取沟通模式 → 进化引擎学习 → 全租户共享
+ */
+@RestController
+@RequestMapping("/workflow/lobster/sales-corpus")
+public class LobsterSalesCorpusController extends BaseController {
+
+    @Autowired(required = false)
+    private SalesCorpusAnalyzer corpusAnalyzer;
+
+    @Autowired
+    private ILobsterSalesCorpusService salesCorpusService;
+// todo 总后台无法获取companyId
+//    /**
+//     * AI分析销冠语料 — 触发AI提取沟通模式
+//     */
+//    @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
+//    @PostMapping("/analyze")
+//    public AjaxResult analyze() {
+
+//        LoginUser loginUser = getLoginUser();
+//        Long companyId = loginUser.getCompany().getCompanyId();
+
+//        if (corpusAnalyzer == null) return AjaxResult.error("语料分析器未初始化");
+
+//        AnalysisReport report = corpusAnalyzer.analyzeCorpus(companyId);
+
+//        Map<String, Object> result = new LinkedHashMap<>();
+//        result.put("totalEntries", report.getTotalEntries());
+//        result.put("overallScore", report.getOverallScore());
+//        result.put("summary", report.getSummary());
+//        result.put("questionPatterns", report.getQuestionPatterns());
+//        result.put("answerPatterns", report.getAnswerPatterns());
+//        result.put("trustStrategies", report.getTrustStrategies());
+//        result.put("closingSkills", report.getClosingSkills());
+//        result.put("objectionHandling", report.getObjectionHandling());
+//        result.put("personalityTraits", report.getPersonalityTraits());
+//        return AjaxResult.success(result);
+//    }
+
+}

+ 125 - 4
fs-admin/src/main/java/com/fs/admin/controller/tenant/TenantInfoController.java

@@ -15,6 +15,7 @@ import com.fs.common.exception.CustomException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.datasource.TenantDataSourceContextHelper;
 import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
@@ -30,8 +31,10 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 
 /**
@@ -56,6 +59,9 @@ public class TenantInfoController extends BaseController
 
     @Autowired
     private TenantDataSourceManager tenantDataSourceManager;
+
+    @Autowired
+    private TenantDataSourceContextHelper tenantContextHelper;
     /**
      * 查询租户基础信息列表
      */
@@ -378,7 +384,12 @@ public class TenantInfoController extends BaseController
             return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
         }
         menu.setUpdateBy(getUsername());
-        return toAjax(tenantInfoService.updateMenu(menu));
+        int result = tenantInfoService.updateMenu(menu);
+        if (result > 0) {
+            SysMenu fullMenu = tenantInfoService.selectMenuById(menu.getMenuId());
+            CompletableFuture.runAsync(() -> syncSysMenuUpdateToTenants(fullMenu));
+        }
+        return toAjax(result);
     }
 
     /**
@@ -402,7 +413,12 @@ public class TenantInfoController extends BaseController
             return AjaxResult.error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己");
         }
         menu.setUpdateBy(getUsername());
-        return toAjax(tenantInfoService.updateComMenu(menu));
+        int result = tenantInfoService.updateComMenu(menu);
+        if (result > 0) {
+            TenantCompanyMenu fullMenu = tenantInfoService.getTenantComMenu(menu.getMenuId());
+            CompletableFuture.runAsync(() -> syncComMenuUpdateToTenants(fullMenu));
+        }
+        return toAjax(result);
     }
 
     @PreAuthorize("@ss.hasPermi('system:menu:remove')")
@@ -414,7 +430,11 @@ public class TenantInfoController extends BaseController
         {
             return AjaxResult.error("存在子菜单,不允许删除");
         }
-        return toAjax(tenantInfoService.deleteMenuById(menuId));
+        int result = tenantInfoService.deleteMenuById(menuId);
+        if (result > 0) {
+            CompletableFuture.runAsync(() -> syncSysMenuDeleteToTenants(menuId));
+        }
+        return toAjax(result);
     }
 
     @PreAuthorize("@ss.hasPermi('system:menu:remove')")
@@ -426,6 +446,107 @@ public class TenantInfoController extends BaseController
         {
             return AjaxResult.error("存在子菜单,不允许删除");
         }
-        return toAjax(tenantInfoService.deleteComMenuById(menuId));
+        int result = tenantInfoService.deleteComMenuById(menuId);
+        if (result > 0) {
+            CompletableFuture.runAsync(() -> syncComMenuDeleteToTenants(menuId));
+        }
+        return toAjax(result);
+    }
+
+    // ========== 菜单模板变更 → 异步同步到所有启用租户库 ==========
+
+    /**
+     * 同步 sys 菜单修改到所有 status=1 的租户库(仅已存在该菜单的租户执行 upsert)
+     */
+    private void syncSysMenuUpdateToTenants(SysMenu menu) {
+        List<TenantInfo> tenants = getActiveTenantsForSync();
+        for (TenantInfo tenant : tenants) {
+            try {
+                tenantContextHelper.executeInTenant(tenant, () -> {
+                    int count = tenantInfoMapper.countTenantSysMenuById(menu.getMenuId());
+                    if (count > 0) {
+                        tenantInfoMapper.upsertSysMenu(Collections.singletonList(menu));
+                    }
+                    return null;
+                });
+            } catch (Exception e) {
+                log.error("同步sys菜单修改失败: tenant={}, menuId={}", tenant.getTenantCode(), menu.getMenuId(), e);
+            }
+        }
+    }
+
+    /**
+     * 同步 sys 菜单删除到所有 status=1 的租户库(仅已存在该菜单的租户执行删除)
+     */
+    private void syncSysMenuDeleteToTenants(Long menuId) {
+        List<TenantInfo> tenants = getActiveTenantsForSync();
+        for (TenantInfo tenant : tenants) {
+            try {
+                tenantContextHelper.executeInTenant(tenant, () -> {
+                    int count = tenantInfoMapper.countTenantSysMenuById(menuId);
+                    if (count > 0) {
+                        List<Long> menuIds = Collections.singletonList(menuId);
+                        tenantInfoMapper.deleteSysRoleMenuByMenuIds(menuIds);
+                        tenantInfoMapper.deleteTenantSysMenuByIds(menuIds);
+                    }
+                    return null;
+                });
+            } catch (Exception e) {
+                log.error("同步sys菜单删除失败: tenant={}, menuId={}", tenant.getTenantCode(), menuId, e);
+            }
+        }
+    }
+
+    /**
+     * 同步 com 菜单修改到所有 status=1 的租户库(仅已存在该菜单的租户执行 upsert)
+     */
+    private void syncComMenuUpdateToTenants(TenantCompanyMenu menu) {
+        List<TenantInfo> tenants = getActiveTenantsForSync();
+        for (TenantInfo tenant : tenants) {
+            try {
+                tenantContextHelper.executeInTenant(tenant, () -> {
+                    int count = tenantInfoMapper.countTenantComMenuById(menu.getMenuId());
+                    if (count > 0) {
+                        tenantInfoMapper.upsertComMenu(Collections.singletonList(menu));
+                    }
+                    return null;
+                });
+            } catch (Exception e) {
+                log.error("同步com菜单修改失败: tenant={}, menuId={}", tenant.getTenantCode(), menu.getMenuId(), e);
+            }
+        }
+    }
+
+    /**
+     * 同步 com 菜单删除到所有 status=1 的租户库(仅已存在该菜单的租户执行删除)
+     */
+    private void syncComMenuDeleteToTenants(Long menuId) {
+        List<TenantInfo> tenants = getActiveTenantsForSync();
+        for (TenantInfo tenant : tenants) {
+            try {
+                tenantContextHelper.executeInTenant(tenant, () -> {
+                    int count = tenantInfoMapper.countTenantComMenuById(menuId);
+                    if (count > 0) {
+                        List<Long> menuIds = Collections.singletonList(menuId);
+                        tenantInfoMapper.deleteComRoleMenuByMenuIds(menuIds);
+                        tenantInfoMapper.deleteTenantComMenuByIds(menuIds);
+                    }
+                    return null;
+                });
+            } catch (Exception e) {
+                log.error("同步com菜单删除失败: tenant={}, menuId={}", tenant.getTenantCode(), menuId, e);
+            }
+        }
+    }
+
+    /**
+     * 查询所有 status=1(启用)的租户,在主库执行
+     */
+    private List<TenantInfo> getActiveTenantsForSync() {
+        return tenantContextHelper.executeInMaster(() -> {
+            TenantInfo query = new TenantInfo();
+            query.setStatus(1);
+            return tenantInfoService.selectTenantInfoList(query);
+        });
     }
 }

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

@@ -260,4 +260,8 @@ public interface CompanyMapper
     List<CompanyVO> getCompanyDropList();
 
     String getGateWayList(@Param("companyId") Long companyId);
+
+    /** 管理端:查询所有有效租户列表 */
+    @Select("SELECT company_id AS id, company_name, domain, status FROM company_info WHERE del_flag = 0 ORDER BY company_id")
+    List<java.util.Map<String, Object>> selectAdminCompanyList();
 }

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

@@ -113,6 +113,12 @@ public interface LobsterAuxiliaryMapper {
     int deleteDeadLetter(@Param("id") Long id);
     int ensureDeadLetterTable();
 
+    /** 统计待处理死信数量(可选租户过滤) */
+    Integer countDeadLetterPending(@Param("companyId") Long companyId);
+
+    /** 统计今日Token消耗总量(可选租户过滤) */
+    Long sumTodayTokens(@Param("companyId") Long companyId);
+
     // === lobster_e2e_test ===
     int insertE2eTest(@Param("companyId") Long companyId,
                       @Param("testName") String testName,

+ 42 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java

@@ -18,9 +18,51 @@ public interface LobsterEvolutionSuggestionMapper extends BaseMapper<LobsterEvol
     @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 1")
     Integer countAppliedByCompanyId(@Param("companyId") Long companyId);
 
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = #{status}")
+    Integer countByStatus(@Param("companyId") Long companyId, @Param("status") int status);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion")
+    Integer countAll();
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE status = #{status}")
+    Integer countAllByStatus(@Param("status") int status);
+
     @Select("SELECT * FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND workflow_id = #{workflowId} ORDER BY create_time DESC")
     List<LobsterEvolutionSuggestion> selectByCompanyAndWorkflow(@Param("companyId") Long companyId, @Param("workflowId") Long workflowId);
 
+    /** 列表查询(可选 companyId),LIMIT 限制 */
+    @Select("<script>" +
+            "SELECT * FROM lobster_evolution_suggestion " +
+            "<where>" +
+            "<if test='companyId != null'>AND company_id = #{companyId}</if>" +
+            "</where>" +
+            "ORDER BY create_time DESC LIMIT #{limit}" +
+            "</script>")
+    List<LobsterEvolutionSuggestion> selectList(@Param("companyId") Long companyId, @Param("limit") int limit);
+
+    /** 按状态查询(可选 companyId) */
+    @Select("<script>" +
+            "SELECT * FROM lobster_evolution_suggestion " +
+            "<where>" +
+            "<if test='companyId != null'>AND company_id = #{companyId}</if>" +
+            "AND status = #{status}" +
+            "</where>" +
+            "ORDER BY create_time DESC LIMIT #{limit}" +
+            "</script>")
+    List<LobsterEvolutionSuggestion> selectListByStatus(@Param("companyId") Long companyId,
+                                                         @Param("status") int status,
+                                                         @Param("limit") int limit);
+
     @Update("UPDATE lobster_evolution_suggestion SET status = 1, apply_time = NOW() WHERE id = #{id}")
     int markApplied(@Param("id") Long id);
+
+    @Select("<script>" +
+            "SELECT id, company_id, workflow_id, node_code, suggestion_type, reason, confidence, status, create_time " +
+            "FROM lobster_evolution_suggestion " +
+            "<where>" +
+            "<if test='companyId != null'>AND company_id = #{companyId}</if>" +
+            "</where>" +
+            "ORDER BY create_time DESC LIMIT #{limit}" +
+            "</script>")
+    List<java.util.Map<String, Object>> selectFieldsList(@Param("companyId") Long companyId, @Param("limit") int limit);
 }

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

@@ -25,4 +25,7 @@ public interface LobsterWorkflowInstanceMapper {
 
     /** 仅取该实例所属模板 ID(用于动态节点生成) */
     Long selectWorkflowIdById(@Param("id") Long id);
+
+    /** 按状态统计实例数量(可选租户过滤) */
+    Integer countByStatus(@Param("companyId") Long companyId, @Param("status") String status);
 }

+ 27 - 0
fs-service/src/main/java/com/fs/company/service/workflow/ILobsterEvolutionSuggestionService.java

@@ -0,0 +1,27 @@
+package com.fs.company.service.workflow;
+
+import com.fs.company.domain.LobsterEvolutionSuggestion;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 龙虾引擎进化建议管理服务
+ */
+public interface ILobsterEvolutionSuggestionService {
+
+    /** 列表查询(可选过滤) */
+    List<Map<String, Object>> listSuggestions(Long companyId);
+
+    /** 按状态查询待审核/已审核 */
+    List<LobsterEvolutionSuggestion> listByStatus(Long companyId, int status, int limit);
+
+    /** 进化统计 */
+    Map<String, Object> getStats(Long companyId);
+
+    /** 引擎进化指标 */
+    Map<String, Object> getEvolutionMetrics(Long companyId);
+
+    /** 待审核列表 */
+    List<LobsterEvolutionSuggestion> listPendingAudit(Long companyId, int limit);
+}

+ 11 - 0
fs-service/src/main/java/com/fs/company/service/workflow/ILobsterInstanceStatsService.java

@@ -0,0 +1,11 @@
+package com.fs.company.service.workflow;
+
+import java.util.Map;
+
+/**
+ * 龙虾引擎实例统计服务
+ */
+public interface ILobsterInstanceStatsService {
+
+    Map<String, Object> getStats(Long companyId);
+}

+ 22 - 0
fs-service/src/main/java/com/fs/company/service/workflow/IWorkflowTemplateAdminService.java

@@ -0,0 +1,22 @@
+package com.fs.company.service.workflow;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 工作流模板/节点管理端服务(跨模块 company_workflow_lobster 表)
+ */
+public interface IWorkflowTemplateAdminService {
+
+    /** 工作流模板列表(可选租户过滤) */
+    List<Map<String, Object>> listTemplates(Long companyId);
+
+    /** 已发布模板列表 */
+    List<Map<String, Object>> listPublishedTemplates();
+
+    /** 获取模板 + 节点树 */
+    Map<String, Object> getTemplateWithNodes(Long workflowId);
+
+    /** 保存模板节点(先删后插) */
+    void saveTemplateNodes(Map<String, Object> body);
+}

+ 86 - 0
fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterEvolutionSuggestionServiceImpl.java

@@ -0,0 +1,86 @@
+package com.fs.company.service.workflow.impl;
+
+import com.fs.company.domain.LobsterEvolutionSuggestion;
+import com.fs.company.mapper.LobsterEvolutionLogMapper;
+import com.fs.company.mapper.LobsterEvolutionSuggestionMapper;
+import com.fs.company.service.workflow.ILobsterEvolutionSuggestionService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class LobsterEvolutionSuggestionServiceImpl implements ILobsterEvolutionSuggestionService {
+
+    private static final Logger log = LoggerFactory.getLogger(LobsterEvolutionSuggestionServiceImpl.class);
+
+    @Autowired
+    private LobsterEvolutionSuggestionMapper suggestionMapper;
+
+    @Autowired
+    private LobsterEvolutionLogMapper evolutionLogMapper;
+
+    @Override
+    public List<Map<String, Object>> listSuggestions(Long companyId) {
+        try {
+            return suggestionMapper.selectFieldsList(companyId, 200);
+        } catch (Exception e) {
+            log.warn("[EvolutionSuggestion] 列表查询异常: companyId={}", companyId, e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public List<LobsterEvolutionSuggestion> listByStatus(Long companyId, int status, int limit) {
+        try {
+            if (status < 0) {
+                return suggestionMapper.selectList(companyId, limit);
+            }
+            return suggestionMapper.selectListByStatus(companyId, status, limit);
+        } catch (Exception e) {
+            log.warn("[EvolutionSuggestion] 按状态查询异常: companyId={}, status={}", companyId, status, e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public Map<String, Object> getStats(Long companyId) {
+        Map<String, Object> stats = new HashMap<>();
+        try {
+            stats.put("total", suggestionMapper.countByCompanyId(companyId));
+            stats.put("pending", suggestionMapper.countByStatus(companyId, 0));
+            stats.put("approved", suggestionMapper.countByStatus(companyId, 1));
+            stats.put("rejected", suggestionMapper.countByStatus(companyId, 2));
+        } catch (Exception e) {
+            log.warn("[EvolutionSuggestion] 统计异常: companyId={}", companyId, e);
+            stats.put("total", 0); stats.put("pending", 0);
+            stats.put("approved", 0); stats.put("rejected", 0);
+        }
+        return stats;
+    }
+
+    @Override
+    public Map<String, Object> getEvolutionMetrics(Long companyId) {
+        Map<String, Object> data = new HashMap<>();
+        try {
+            data.put("totalEvolutions", evolutionLogMapper.countByCompanyId(companyId));
+            data.put("appliedCount", suggestionMapper.countByStatus(companyId, 1));
+            data.put("pendingCount", suggestionMapper.countByStatus(companyId, 0));
+        } catch (Exception e) {
+            log.warn("[EvolutionSuggestion] 指标查询异常: companyId={}", companyId, e);
+            data.put("totalEvolutions", 0); data.put("appliedCount", 0); data.put("pendingCount", 0);
+        }
+        return data;
+    }
+
+    @Override
+    public List<LobsterEvolutionSuggestion> listPendingAudit(Long companyId, int limit) {
+        try {
+            return suggestionMapper.selectListByStatus(companyId, 0, limit);
+        } catch (Exception e) {
+            return new ArrayList<>();
+        }
+    }
+}

+ 48 - 0
fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterInstanceStatsServiceImpl.java

@@ -0,0 +1,48 @@
+package com.fs.company.service.workflow.impl;
+
+import com.fs.company.mapper.LobsterAuxiliaryMapper;
+import com.fs.company.mapper.LobsterWorkflowInstanceMapper;
+import com.fs.company.service.workflow.ILobsterInstanceStatsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class LobsterInstanceStatsServiceImpl implements ILobsterInstanceStatsService {
+
+    private static final Logger log = LoggerFactory.getLogger(LobsterInstanceStatsServiceImpl.class);
+
+    @Autowired
+    private LobsterWorkflowInstanceMapper instanceMapper;
+
+    @Autowired(required = false)
+    private LobsterAuxiliaryMapper auxMapper;
+
+    @Override
+    public Map<String, Object> getStats(Long companyId) {
+        Map<String, Object> stats = new HashMap<>();
+        try {
+            stats.put("running", instanceMapper.countByStatus(companyId, "running"));
+            stats.put("paused", instanceMapper.countByStatus(companyId, "paused"));
+            stats.put("completed", instanceMapper.countByStatus(companyId, "completed"));
+
+            Integer dead = auxMapper != null ? auxMapper.countDeadLetterPending(companyId) : 0;
+            stats.put("deadLetters", dead != null ? dead : 0);
+
+            Long tokens = auxMapper != null ? auxMapper.sumTodayTokens(companyId) : 0L;
+            stats.put("todayTokens", tokens != null ? String.valueOf(tokens) : "0");
+        } catch (Exception e) {
+            log.warn("[InstanceStats] 统计异常: companyId={}, {}", companyId, e.getMessage());
+            stats.put("running", 0);
+            stats.put("paused", 0);
+            stats.put("completed", 0);
+            stats.put("deadLetters", 0);
+            stats.put("todayTokens", "0");
+        }
+        return stats;
+    }
+}

+ 162 - 0
fs-service/src/main/java/com/fs/company/service/workflow/impl/WorkflowTemplateAdminServiceImpl.java

@@ -0,0 +1,162 @@
+package com.fs.company.service.workflow.impl;
+
+import com.fs.company.domain.CompanyWorkflowLobster;
+import com.fs.company.domain.CompanyWorkflowLobsterNode;
+import com.fs.company.mapper.CompanyWorkflowLobsterMapper;
+import com.fs.company.mapper.CompanyWorkflowLobsterNodeMapper;
+import com.fs.company.service.workflow.IWorkflowTemplateAdminService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+@Service
+public class WorkflowTemplateAdminServiceImpl implements IWorkflowTemplateAdminService {
+
+    private static final Logger log = LoggerFactory.getLogger(WorkflowTemplateAdminServiceImpl.class);
+
+    @Autowired
+    private CompanyWorkflowLobsterMapper templateMapper;
+
+    @Autowired
+    private CompanyWorkflowLobsterNodeMapper nodeMapper;
+
+    @Override
+    public List<Map<String, Object>> listTemplates(Long companyId) {
+        try {
+            List<CompanyWorkflowLobster> list;
+            if (companyId != null) {
+                list = templateMapper.selectTemplateList(companyId);
+            } else {
+                list = templateMapper.selectTemplateList(null);
+            }
+            List<Map<String, Object>> result = new ArrayList<>();
+            if (list != null) {
+                for (CompanyWorkflowLobster t : list) {
+                    Map<String, Object> m = new LinkedHashMap<>();
+                    m.put("id", t.getId());
+                    m.put("companyId", t.getCompanyId());
+                    m.put("templateCode", t.getTemplateCode());
+                    m.put("templateName", t.getTemplateName());
+                    m.put("industryType", t.getIndustryType());
+                    m.put("status", t.getStatus());
+                    m.put("version", t.getVersion());
+                    m.put("canvasData", t.getCanvasData());
+                    m.put("updateTime", t.getUpdateTime());
+                    m.put("createTime", t.getCreateTime());
+                    result.add(m);
+                }
+            }
+            return result;
+        } catch (Exception e) {
+            log.warn("[TemplateAdmin] 列表查询异常: companyId={}", companyId, e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public List<Map<String, Object>> listPublishedTemplates() {
+        try {
+            List<CompanyWorkflowLobster> list = templateMapper.selectTemplateListByStatus(null, 1);
+            List<Map<String, Object>> result = new ArrayList<>();
+            if (list != null) {
+                for (CompanyWorkflowLobster t : list) {
+                    Map<String, Object> m = new LinkedHashMap<>();
+                    m.put("id", t.getId());
+                    m.put("templateCode", t.getTemplateCode());
+                    m.put("templateName", t.getTemplateName());
+                    m.put("industryType", t.getIndustryType());
+                    m.put("description", t.getDescription());
+                    m.put("status", t.getStatus());
+                    m.put("version", t.getVersion());
+                    m.put("createTime", t.getCreateTime());
+                    m.put("updateTime", t.getUpdateTime());
+                    result.add(m);
+                }
+            }
+            return result;
+        } catch (Exception e) {
+            log.warn("[TemplateAdmin] 已发布模板查询异常", e);
+            return new ArrayList<>();
+        }
+    }
+
+    @Override
+    public Map<String, Object> getTemplateWithNodes(Long workflowId) {
+        Map<String, Object> result = new HashMap<>();
+        try {
+            CompanyWorkflowLobster template = templateMapper.selectTemplateByIdAndCompanyId(workflowId, null);
+            if (template != null) {
+                Map<String, Object> tm = new LinkedHashMap<>();
+                tm.put("id", template.getId());
+                tm.put("templateCode", template.getTemplateCode());
+                tm.put("templateName", template.getTemplateName());
+                tm.put("industryType", template.getIndustryType());
+                tm.put("description", template.getDescription());
+                tm.put("status", template.getStatus());
+                result.put("template", tm);
+            }
+            List<CompanyWorkflowLobsterNode> nodes = nodeMapper.selectByWorkflowId(workflowId);
+            result.put("nodes", nodes != null ? nodes : new ArrayList<>());
+        } catch (Exception e) {
+            log.warn("[TemplateAdmin] 获取模板节点异常: workflowId={}", workflowId, e);
+            result.put("template", null);
+            result.put("nodes", new ArrayList<>());
+        }
+        return result;
+    }
+
+    @Override
+    public void saveTemplateNodes(Map<String, Object> body) {
+        Long workflowId = toLong(body.get("workflowId"));
+        if (workflowId == null) throw new IllegalArgumentException("workflowId必填");
+
+        // 更新模板头
+        String templateName = (String) body.get("templateName");
+        if (templateName != null) {
+            CompanyWorkflowLobster template = new CompanyWorkflowLobster();
+            template.setId(workflowId);
+            template.setTemplateName(templateName);
+            template.setIndustryType((String) body.get("industryType"));
+            template.setDescription((String) body.get("description"));
+            templateMapper.updateTemplateById(template);
+        }
+
+        // 软删除旧节点
+        nodeMapper.deleteByWorkflowId(workflowId);
+
+        // 插入新节点
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> nodeList = (List<Map<String, Object>>) body.get("nodes");
+        if (nodeList != null && !nodeList.isEmpty()) {
+            List<CompanyWorkflowLobsterNode> entities = new ArrayList<>();
+            for (Map<String, Object> n : nodeList) {
+                CompanyWorkflowLobsterNode node = new CompanyWorkflowLobsterNode();
+                node.setWorkflowId(workflowId);
+                node.setNodeCode((String) n.getOrDefault("nodeCode", ""));
+                node.setNodeName((String) n.getOrDefault("nodeName", ""));
+                node.setNodeType(toInt(n, "nodeType", 2));
+                node.setSortNo(toInt(n, "sortNo", 0));
+                node.setNextNodeCode((String) n.getOrDefault("nextNodeCode", null));
+                node.setMessageTemplate((String) n.getOrDefault("messageTemplate", null));
+                node.setConditionExpr((String) n.getOrDefault("conditionExpr", null));
+                node.setNodeConfig((String) n.getOrDefault("nodeConfig", null));
+                entities.add(node);
+            }
+            nodeMapper.batchInsert(entities);
+        }
+    }
+
+    private int toInt(Map<String, Object> map, String key, int def) {
+        Object v = map.get(key);
+        return v instanceof Number ? ((Number) v).intValue() : def;
+    }
+
+    private Long toLong(Object v) {
+        if (v == null) return null;
+        if (v instanceof Number) return ((Number) v).longValue();
+        try { return Long.valueOf(v.toString()); } catch (Exception e) { return null; }
+    }
+}

+ 2 - 2
fs-service/src/main/java/com/fs/hisStore/service/impl/FsUserSignScrmServiceImpl.java

@@ -122,7 +122,7 @@ public class FsUserSignScrmServiceImpl implements IFsUserSignScrmService
     @Override
     @Transactional
     public Long sign(FsUserScrm user) {
-        String json=configService.selectConfigByKey("store.sign");
+        String json=configService.selectConfigByKey("his.sign");
         if(StringUtils.isEmpty(json)) {
             throw new CustomException("请先配置签到天数");
         }
@@ -194,7 +194,7 @@ public class FsUserSignScrmServiceImpl implements IFsUserSignScrmService
 
     @Override
     public Long getSign(FsUserScrm user) {
-        String json=configService.selectConfigByKey("store.sign");
+        String json=configService.selectConfigByKey("his.sign");
         if(StringUtils.isEmpty(json)) {
             throw new CustomException("请先配置签到天数");
         }

+ 6 - 0
fs-service/src/main/java/com/fs/tenant/mapper/TenantInfoMapper.java

@@ -152,6 +152,12 @@ public interface TenantInfoMapper extends BaseMapper<TenantInfo> {
 
     int deleteComRoleMenuByMenuIds(@Param("menuIds") List<Long> menuIds);
 
+    /** 检查租户库 sys_menu 中是否存在指定菜单(须在租户数据源下调用) */
+    int countTenantSysMenuById(@Param("menuId") Long menuId);
+
+    /** 检查租户库 company_menu 中是否存在指定菜单(须在租户数据源下调用) */
+    int countTenantComMenuById(@Param("menuId") Long menuId);
+
     TenantInfo getTenByCode(String code);
 
     List<FeePlanItem> selectFeeItem(String feePlanCode);

+ 3 - 0
fs-service/src/main/resources/db/tenant-initTable-migration.sql

@@ -984,6 +984,9 @@ CREATE TABLE IF NOT EXISTS `lobster_sales_corpus` (
   `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
   `update_by` varchar(64) DEFAULT '',
   `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `content` text,
+  `source` varchar(50) DEFAULT NULL,
+  `usage_count` int DEFAULT '0',
   PRIMARY KEY (`id`),
   KEY `idx_scenario` (`scenario`),
   KEY `idx_company` (`company_id`)

+ 1837 - 2
fs-service/src/main/resources/db/tenant-initTable.sql

@@ -1050,6 +1050,7 @@ CREATE TABLE `chat_msg`
     `nick_name`       varchar(200)   NULL DEFAULT NULL,
     `avatar`          varchar(200)   NULL DEFAULT NULL,
     `user_type`       tinyint(1) NULL DEFAULT 1 COMMENT '用户类型 1微信用户 2小程序用户 3销售用户',
+    `channel_type`    varchar(30) NULL DEFAULT 'QW' COMMENT '渠道类型',
     PRIMARY KEY (`msg_id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '聊天消息记录表' ROW_FORMAT = DYNAMIC;
 
@@ -1113,6 +1114,15 @@ CREATE TABLE `chat_session`
     `user_type`        tinyint(1) NULL DEFAULT 1 COMMENT '用户类型 1微信用户 2小程序用户 3销售用户',
     `nick_name`        varchar(200)   NULL DEFAULT NULL,
     `avatar`           varchar(200)   NULL DEFAULT NULL,
+    `contact_id`       bigint NULL DEFAULT NULL COMMENT '触点ID',
+    `channel_type`     varchar(30) NULL DEFAULT 'QW' COMMENT '渠道类型: QW/WX/IM/WHATSAPP/...',
+    `channel_source_id` varchar(128) NULL DEFAULT NULL COMMENT '源平台用户ID',
+    `channel_source_type` varchar(100) NULL DEFAULT NULL COMMENT '源平台表名',
+    `last_msg`         varchar(500) NULL DEFAULT NULL COMMENT '最后消息摘要',
+    `last_msg_time`    datetime NULL DEFAULT NULL COMMENT '最后消息时间',
+    `unread_count`     int NULL DEFAULT 0 COMMENT '未读数',
+    `instance_id`      bigint NULL DEFAULT NULL COMMENT '工作流实例ID',
+    `external_user_id` varchar(128) NULL DEFAULT NULL COMMENT '外部用户ID',
     PRIMARY KEY (`session_id`) USING BTREE
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 COMMENT = '会话表' ROW_FORMAT = DYNAMIC;
 
@@ -15833,7 +15843,8 @@ CREATE TABLE `qw_user`
     `send_msg_type`    int NULL DEFAULT 0 COMMENT '发送消息类型0侧边栏1pad',
     `is_auto`          char(2)   NULL DEFAULT '00' COMMENT '是否自动发课 00、禁用,01、启用',
     `video_get_status` int NULL DEFAULT 1 COMMENT '是否获取视频号消息(0否1是)',
-    `ai_status`        tinyint(1) NULL DEFAULT 0 COMMENT '角色状态 默认为0 0为启用 1为禁用',    `qw_open_user_id` varchar(100)   NULL DEFAULT NULL,
+    `ai_status`        tinyint(1) NULL DEFAULT 0 COMMENT '角色状态 默认为0 0为启用 1为禁用',
+    `qw_open_user_id` varchar(100)   NULL DEFAULT NULL,
 
     PRIMARY KEY (`id`) USING BTREE,
     UNIQUE INDEX `5`(`qw_user_id` ASC, `corp_id` ASC) USING BTREE,
@@ -18182,6 +18193,8 @@ CREATE TABLE `company_workflow_lobster_node`
     `message_template` text COMMENT '消息模板',
     `condition_expr`   text COMMENT '条件表达式JSON',
     `node_config`      text COMMENT '节点配置JSON',
+    `scene_code`       varchar(64)           DEFAULT NULL COMMENT 'AI场景编码',
+    `model_name`       varchar(128)          DEFAULT NULL COMMENT '指定模型名称',
     `greeting_config`  text COMMENT '问候配置JSON',
     `create_by`        varchar(64)           DEFAULT NULL,
     `create_time`      datetime              DEFAULT CURRENT_TIMESTAMP,
@@ -18189,10 +18202,11 @@ CREATE TABLE `company_workflow_lobster_node`
     `update_time`      datetime              DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     `del_flag`         tinyint      NOT NULL DEFAULT '0',
     `send_time`        time                  DEFAULT NULL COMMENT '发送时间',
-    `max_round`        int                   DEFAULT '0' COMMENT '最大循环数',
+    `max_rounds`       int                   DEFAULT '0' COMMENT '最大循环数',
     PRIMARY KEY (`id`),
     KEY                `idx_workflow_id` (`workflow_id`),
     KEY                `idx_node_code` (`node_code`),
+    KEY                `idx_node_scene_code` (`scene_code`),
     KEY                `idx_cwln_workflow_del_sort` (`workflow_id`,`del_flag`,`sort_no`)
 ) ENGINE=InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='工作流龙虾节点';
 
@@ -18789,3 +18803,1824 @@ CREATE TABLE `sys_redpacket_config_more` (
   PRIMARY KEY (`id`),
   KEY `idx_mch_id` (`mch_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='多商户配置';
+
+
+-- ============================================================================
+-- tenant-initTable 补全:与 Java @TableName / Mapper 一致的缺失表
+-- ============================================================================
+
+-- ----------------------------
+-- Table structure for cc_llm_kb
+-- ----------------------------
+DROP TABLE IF EXISTS `cc_llm_kb`;
+CREATE TABLE `cc_llm_kb` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) DEFAULT NULL COMMENT '知识库名称',
+  `title` varchar(200) DEFAULT NULL COMMENT '标题',
+  `content` text COMMENT '内容',
+  `cat_id` bigint DEFAULT NULL COMMENT '分类ID',
+  `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI外呼LLM知识库';
+
+-- ----------------------------
+-- Table structure for company_ai_provider
+-- ----------------------------
+DROP TABLE IF EXISTS `company_ai_provider`;
+CREATE TABLE `company_ai_provider` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint DEFAULT NULL,
+    `provider_code` varchar(50) DEFAULT NULL,
+    `provider_name` varchar(100) DEFAULT NULL,
+    `api_key` varchar(500) DEFAULT NULL,
+    `api_endpoint` varchar(500) DEFAULT NULL,
+    `model_name` varchar(100) DEFAULT NULL,
+    `max_tokens` int DEFAULT 4096,
+    `temperature` double DEFAULT 0.7,
+    `is_default` int DEFAULT 0,
+    `enabled` int DEFAULT 1,
+    `del_flag` int DEFAULT 0,
+    `create_by` varchar(64) DEFAULT NULL,
+    `create_time` datetime DEFAULT NULL,
+    `update_by` varchar(64) DEFAULT NULL,
+    `update_time` datetime DEFAULT NULL,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='企业AI供应商配置';
+
+-- ----------------------------
+-- Table structure for company_ai_workflow_company_user
+-- ----------------------------
+DROP TABLE IF EXISTS `company_ai_workflow_company_user`;
+CREATE TABLE `company_ai_workflow_company_user` (
+    `workflow_id` bigint NOT NULL COMMENT '工作流ID',
+    `company_user_id` bigint NOT NULL COMMENT '公司员工ID',
+    PRIMARY KEY (`workflow_id`, `company_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI工作流-员工关联';
+
+-- ----------------------------
+-- Table structure for company_ai_workflow_company_voice
+-- ----------------------------
+DROP TABLE IF EXISTS `company_ai_workflow_company_voice`;
+CREATE TABLE `company_ai_workflow_company_voice` (
+    `workflow_id` bigint NOT NULL COMMENT '工作流ID',
+    `company_user_id` bigint NOT NULL COMMENT '公司员工ID',
+    `node_key` varchar(64) NOT NULL COMMENT '节点Key',
+    PRIMARY KEY (`workflow_id`, `company_user_id`, `node_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI工作流-员工语音节点关联';
+
+-- ----------------------------
+-- Table structure for company_lobster_profile_config
+-- ----------------------------
+DROP TABLE IF EXISTS `company_lobster_profile_config`;
+CREATE TABLE `company_lobster_profile_config` (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    company_id BIGINT NOT NULL,
+    field_key VARCHAR(64) NOT NULL COMMENT '客户画像字段(如 age, gender, occupation)',
+    `field_label` VARCHAR(64) COMMENT '字段中文名',
+    `field_type` VARCHAR(32) DEFAULT NULL COMMENT '字段类型',
+    `extract_rule` TEXT COMMENT '提取规则(关键词/正则/AI prompt 模板)',
+    extract_mode VARCHAR(32) DEFAULT 'ai' COMMENT 'keyword/regex/ai',
+    enabled TINYINT DEFAULT 1,
+    sort_order INT DEFAULT 0,
+    create_by VARCHAR(64),
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_by VARCHAR(64),
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY idx_company (company_id),
+    KEY idx_company_field (company_id, field_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾画像配置';
+
+-- ----------------------------
+-- Table structure for company_lobster_sensitive_word
+-- ----------------------------
+DROP TABLE IF EXISTS `company_lobster_sensitive_word`;
+CREATE TABLE `company_lobster_sensitive_word` (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    company_id BIGINT NOT NULL,
+    word VARCHAR(128) NOT NULL,
+    category VARCHAR(32) COMMENT '分类:politic/violence/porn/finance/custom',
+    action VARCHAR(32) DEFAULT 'block' COMMENT 'block/replace/warn',
+    replace_text VARCHAR(128) COMMENT '替换文本(action=replace 时用)',
+    enabled TINYINT DEFAULT 1,
+    create_by VARCHAR(64),
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_by VARCHAR(64),
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY idx_company (company_id),
+    KEY idx_company_word (company_id, word)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾敏感词';
+
+-- ----------------------------
+-- Table structure for company_lobster_summary_config
+-- ----------------------------
+DROP TABLE IF EXISTS `company_lobster_summary_config`;
+CREATE TABLE `company_lobster_summary_config` (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    company_id BIGINT NOT NULL,
+    scenario VARCHAR(64) NOT NULL COMMENT '场景:daily/handoff/closing',
+    summary_template TEXT COMMENT '摘要 prompt 模板',
+    max_length INT DEFAULT 500 COMMENT '摘要最大字数',
+    enabled TINYINT DEFAULT 1,
+    create_by VARCHAR(64),
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
+    update_by VARCHAR(64),
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    KEY idx_company (company_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾会话摘要配置';
+
+-- ----------------------------
+-- Table structure for company_sms_device
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_device`;
+CREATE TABLE `company_sms_device` (
+  `device_id`        BIGINT NOT NULL AUTO_INCREMENT COMMENT '设备ID',
+  `tenant_id`        BIGINT NOT NULL COMMENT '所属租户',
+  `company_user_id`  BIGINT DEFAULT NULL COMMENT '绑定销售用户ID(NULL=未分配)',
+  `device_name`      VARCHAR(100) DEFAULT NULL COMMENT '设备名称',
+  `imei`             VARCHAR(50) NOT NULL COMMENT 'IMEI(唯一标识)',
+  `app_version`      VARCHAR(30) DEFAULT NULL COMMENT 'APP版本',
+  `middleware_id`    BIGINT DEFAULT NULL COMMENT '关联中间件ID',
+  `last_heartbeat`   DATETIME DEFAULT NULL COMMENT '最后心跳时间',
+  `status`           TINYINT DEFAULT 0 COMMENT '0离线/1在线/2禁用',
+  `remark`           VARCHAR(500) DEFAULT NULL COMMENT '备注',
+  `create_time`      DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `update_time`      DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`device_id`),
+  UNIQUE KEY `uk_imei` (`imei`),
+  KEY `idx_tenant_id` (`tenant_id`),
+  KEY `idx_company_user_id` (`company_user_id`),
+  KEY `idx_middleware_id` (`middleware_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='手机设备管理';
+
+-- ----------------------------
+-- Table structure for company_voice_robotic_call_blacklist_intercept_log
+-- ----------------------------
+DROP TABLE IF EXISTS `company_voice_robotic_call_blacklist_intercept_log`;
+CREATE TABLE `company_voice_robotic_call_blacklist_intercept_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `company_id` bigint DEFAULT NULL COMMENT '租户/公司ID',
+  `scene_code` varchar(32) NOT NULL COMMENT '业务场景:CALL外呼 SMS短信 ADD_WX加微',
+  `blacklist_id` bigint DEFAULT NULL COMMENT '命中的黑名单记录ID',
+  `target_type` tinyint NOT NULL COMMENT '对象类型:1手机号 2客户ID 3企微客户ID',
+  `target_value` varchar(128) NOT NULL COMMENT '拦截时对象值(手机号加密)',
+  `blacklist_reason` varchar(255) DEFAULT NULL COMMENT '黑名单拉黑原因快照',
+  `intercept_reason` varchar(500) NOT NULL COMMENT '拦截说明',
+  `customer_id` bigint DEFAULT NULL COMMENT 'CRM客户ID',
+  `callee_id` bigint DEFAULT NULL COMMENT '外呼被叫人ID',
+  `robotic_id` bigint DEFAULT NULL COMMENT '外呼任务ID',
+  `company_user_id` bigint DEFAULT NULL COMMENT '操作人ID',
+  `biz_id` varchar(64) DEFAULT NULL COMMENT '业务ID',
+  `biz_trace_id` varchar(64) DEFAULT NULL COMMENT '链路追踪ID',
+  `request_ip` varchar(64) DEFAULT NULL COMMENT '请求IP',
+  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '拦截时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_company_time` (`company_id`, `create_time`),
+  KEY `idx_blacklist_id` (`blacklist_id`),
+  KEY `idx_scene_time` (`scene_code`, `create_time`),
+  KEY `idx_target_value` (`target_type`, `target_value`(32)),
+  KEY `idx_customer_id` (`customer_id`),
+  KEY `idx_robotic_callee` (`robotic_id`, `callee_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='外呼黑名单拦截明细';
+
+-- ----------------------------
+-- Table structure for customer_fact
+-- ----------------------------
+DROP TABLE IF EXISTS `customer_fact`;
+CREATE TABLE `customer_fact` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    external_user_id VARCHAR(128) NOT NULL,
+    instance_id BIGINT DEFAULT NULL,
+    fact_key VARCHAR(200) NOT NULL,
+    fact_value VARCHAR(2000),
+    fact_type VARCHAR(30) DEFAULT 'EXTRACTED' COMMENT 'EXTRACTED/INFERRED/MANUAL',
+    confidence DOUBLE DEFAULT 1.0 COMMENT '1.0=新插入, 衰减至<0.3将被清理',
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_fact (company_id, external_user_id, instance_id, fact_key),
+    INDEX idx_company_user (company_id, external_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for customer_habit
+-- ----------------------------
+DROP TABLE IF EXISTS `customer_habit`;
+CREATE TABLE `customer_habit` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    external_user_id VARCHAR(128) NOT NULL,
+    habit_key VARCHAR(64) NOT NULL,
+    habit_value VARCHAR(512),
+    confidence DOUBLE DEFAULT 0.5,
+    source VARCHAR(32) DEFAULT 'AI',
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_habit (company_id, external_user_id, habit_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for customer_tag
+-- ----------------------------
+DROP TABLE IF EXISTS `customer_tag`;
+CREATE TABLE `customer_tag` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    external_user_id VARCHAR(128) NOT NULL,
+    tag_key VARCHAR(100) NOT NULL,
+    tag_value VARCHAR(500),
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_tag (company_id, external_user_id, tag_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for fee_plan
+-- ----------------------------
+DROP TABLE IF EXISTS `fee_plan`;
+CREATE TABLE `fee_plan` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `plan_code` varchar(64) NOT NULL COMMENT '????',
+  `plan_name` varchar(128) NOT NULL COMMENT '????',
+  `version` int NOT NULL COMMENT '???',
+  `status` varchar(16) NOT NULL DEFAULT 'DRAFT' COMMENT 'DRAFT/PUBLISHED/ARCHIVED',
+  `effective_time` datetime DEFAULT NULL COMMENT '????',
+  `expire_time` datetime DEFAULT NULL COMMENT '????',
+  `remark` varchar(500) DEFAULT NULL,
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_plan_ver` (`plan_code`,`version`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='??????';
+
+-- ----------------------------
+-- Table structure for fee_plan_item
+-- ----------------------------
+DROP TABLE IF EXISTS `fee_plan_item`;
+CREATE TABLE `fee_plan_item` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `plan_code` varchar(64) NOT NULL,
+  `version` int NOT NULL,
+  `item_code` varchar(64) NOT NULL COMMENT 'FLOW_POSTPAID/CALL_OUT/CALL_IN/AI_CALL/SOP_TOKEN/AI_REPLY_TOKEN/ADD_WECHAT/OPEN_ACCOUNT_NON_AI/OPEN_ACCOUNT_AI',
+  `unit` varchar(32) NOT NULL COMMENT 'GB/MIN/TOKEN/COUNT/TIME',
+  `unit_price` decimal(18,6) NOT NULL DEFAULT '0.000000' COMMENT '??',
+  `token_unit` bigint DEFAULT NULL COMMENT 'token????(?100000)',
+  `min_charge_unit` int DEFAULT NULL COMMENT '??????(?????1??)',
+  `enabled` tinyint NOT NULL DEFAULT '1',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_plan_item` (`plan_code`,`version`,`item_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='?????';
+
+-- ----------------------------
+-- Table structure for lobster_api_registry
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_api_registry`;
+CREATE TABLE `lobster_api_registry` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `api_name` varchar(200) NOT NULL,
+  `api_key` varchar(100) NOT NULL,
+  `api_url` varchar(500) DEFAULT NULL,
+  `api_type` varchar(50) DEFAULT NULL,
+  `api_description` text,
+  `request_method` varchar(10) DEFAULT 'POST',
+  `request_headers` text,
+  `request_body_template` text,
+  `response_mapping` text,
+  `enabled` tinyint DEFAULT '1',
+  `category` varchar(100) DEFAULT NULL,
+  `company_id` bigint DEFAULT NULL,
+  `create_by` varchar(64) DEFAULT '',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_by` varchar(64) DEFAULT '',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_api_key` (`api_key`),
+  KEY `idx_category` (`category`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='API注册中心';
+
+-- ----------------------------
+-- Table structure for lobster_channel_plugin_config
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_channel_plugin_config`;
+CREATE TABLE `lobster_channel_plugin_config` (
+  id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+  company_id    BIGINT       NOT NULL COMMENT '租户ID',
+  channel_type  VARCHAR(30)  NOT NULL COMMENT '渠道类型: QW/WX/IM/WHATSAPP/LINE/TELEGRAM/APP_IM/DOUYIN_DM/KUAISHOU_DM/XIAOHONGSHU_DM/TMALL/JD/OTHER',
+  enabled       TINYINT      DEFAULT 0 COMMENT '是否启用: 0-禁用 1-启用',
+  config_json   TEXT         COMMENT '渠道配置JSON(API Key/Webhook URL/Token/phoneNumberId等)',
+  create_time   DATETIME     DEFAULT CURRENT_TIMESTAMP,
+  update_time   DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  UNIQUE KEY uk_company_channel (company_id, channel_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾渠道插件配置表(即插即用多IM通道)';
+
+-- ----------------------------
+-- Table structure for lobster_compliance_audit
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_compliance_audit`;
+CREATE TABLE `lobster_compliance_audit` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    rule_name VARCHAR(200) DEFAULT NULL,
+    severity INT DEFAULT 1,
+    matched_keyword VARCHAR(200) DEFAULT NULL,
+    content_snippet VARCHAR(500) DEFAULT NULL,
+    create_time DATETIME DEFAULT NOW(),
+    INDEX idx_company (company_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_compliance_rule
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_compliance_rule`;
+CREATE TABLE `lobster_compliance_rule` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL,
+  `rule_name` varchar(200) DEFAULT NULL,
+  `rule_type` varchar(50) DEFAULT NULL,
+  `pattern` text,
+  `description` text,
+  `action` varchar(200) DEFAULT NULL,
+  `severity` int DEFAULT NULL,
+  `enabled` int DEFAULT '1',
+  `del_flag` int DEFAULT '0',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_company_id` (`company_id`),
+  KEY `idx_rule_type` (`rule_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾合规规则';
+
+-- ----------------------------
+-- Table structure for lobster_conversation_summary
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_conversation_summary`;
+CREATE TABLE `lobster_conversation_summary` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL,
+  `instance_id` bigint DEFAULT NULL,
+  `contact_id` bigint DEFAULT NULL,
+  `summary_type` varchar(50) DEFAULT NULL,
+  `summary_content` text,
+  `key_points` text,
+  `sentiment_analysis` varchar(200) DEFAULT NULL,
+  `next_action_suggestion` text,
+  `message_count` int DEFAULT '0',
+  `del_flag` int DEFAULT '0',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `idx_company_id` (`company_id`),
+  KEY `idx_instance_id` (`instance_id`),
+  KEY `idx_contact_id` (`contact_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾对话摘要';
+
+-- ----------------------------
+-- Table structure for lobster_dead_letter_queue
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_dead_letter_queue`;
+CREATE TABLE `lobster_dead_letter_queue` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `workflow_instance_id` bigint DEFAULT NULL,
+  `node_code` varchar(100) DEFAULT NULL,
+  `error_message` text,
+  `payload` text,
+  `retry_count` int DEFAULT '0',
+  `status` varchar(20) DEFAULT 'pending',
+  `company_id` bigint DEFAULT NULL,
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_workflow_instance` (`workflow_instance_id`),
+  KEY `idx_status` (`status`),
+    `queue_name` varchar(100) DEFAULT NULL COMMENT '队列名',
+    `error_msg` varchar(500) DEFAULT NULL COMMENT '错误信息') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='死信队列';
+
+-- ----------------------------
+-- Table structure for lobster_dialogue_state
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_dialogue_state`;
+CREATE TABLE `lobster_dialogue_state` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL,
+  `instance_id` bigint NOT NULL,
+  `node_code` varchar(100) NOT NULL,
+  `state_json` text,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_instance_node` (`instance_id`,`node_code`),
+  KEY `idx_company` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- ----------------------------
+-- Table structure for lobster_dynamic_node_impl
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_dynamic_node_impl`;
+CREATE TABLE `lobster_dynamic_node_impl` (
+  id            BIGINT AUTO_INCREMENT PRIMARY KEY,
+  company_id    BIGINT       DEFAULT NULL                COMMENT '租户ID, 0/NULL=全租户通用',
+  node_type     INT          NOT NULL                    COMMENT '陌生节点类型编号',
+  node_type_code VARCHAR(64) DEFAULT NULL                COMMENT '节点编码(如knowledge_retrieval)',
+  fingerprint   VARCHAR(128) NOT NULL                    COMMENT 'nodeType + config 关键字段 hash, 同 fingerprint 复用',
+  sub_dsl_json  MEDIUMTEXT   NOT NULL                    COMMENT 'AI 拆解后的子节点 DSL(JSON Array)',
+  prompt_used   TEXT         DEFAULT NULL                COMMENT '生成时使用的 prompt 备份',
+  source_model  VARCHAR(64)  DEFAULT NULL                COMMENT '生成模型名',
+  quality_score DOUBLE       DEFAULT 0                   COMMENT '质量评分 0-100',
+  exec_count    INT          DEFAULT 0                   COMMENT '累计调用次数',
+  success_count INT          DEFAULT 0                   COMMENT '累计成功次数',
+  avg_duration_ms INT        DEFAULT 0                   COMMENT '平均执行毫秒',
+  status        VARCHAR(16)  DEFAULT 'DRAFT'             COMMENT 'DRAFT/PENDING/ACTIVE/REJECTED',
+  last_exec_time DATETIME    DEFAULT NULL,
+  reviewed_by   VARCHAR(64)  DEFAULT NULL,
+  reviewed_time DATETIME     DEFAULT NULL,
+  reject_reason VARCHAR(255) DEFAULT NULL,
+  create_time   DATETIME     DEFAULT CURRENT_TIMESTAMP,
+  update_time   DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  KEY idx_lookup (node_type, fingerprint, status),
+  KEY idx_company_status (company_id, status),
+  KEY idx_node_type (node_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾动态节点学习产物表';
+
+-- ----------------------------
+-- Table structure for lobster_e2e_run
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_e2e_run`;
+CREATE TABLE `lobster_e2e_run` (
+  `id`                BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `run_id`            VARCHAR(64)  NOT NULL COMMENT '运行ID(UUID)',
+  `company_id`        BIGINT       NULL     COMMENT '公司ID',
+  `template_id`       BIGINT       NULL     COMMENT '工作流模板ID',
+  `instance_id`       BIGINT       NULL     COMMENT '工作流实例ID',
+  `scenario_id`       BIGINT       NULL     COMMENT '测试场景ID',
+  `business_desc`     VARCHAR(500) NULL     COMMENT '业务描述(若是即时生成)',
+  `total_score`       DECIMAL(5,2) NULL     COMMENT '综合评分(0-100)',
+  `passed_node_cnt`   INT          DEFAULT 0 COMMENT '通过节点数',
+  `total_node_cnt`    INT          DEFAULT 0 COMMENT '总节点数',
+  `duration_ms`       BIGINT       DEFAULT 0 COMMENT '总耗时ms',
+  `status`            VARCHAR(20)  DEFAULT 'RUNNING' COMMENT 'RUNNING|SUCCESS|FAILED',
+  `error_msg`         TEXT         NULL     COMMENT '错误信息',
+  `evolution_count`   INT          DEFAULT 0 COMMENT '生成的进化建议数',
+  `create_by`         VARCHAR(64)  NULL,
+  `create_time`       DATETIME     DEFAULT CURRENT_TIMESTAMP,
+  `update_time`       DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_run_id` (`run_id`),
+  KEY `idx_template` (`template_id`),
+  KEY `idx_status_time` (`status`, `create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾E2E测试运行头表';
+
+-- ----------------------------
+-- Table structure for lobster_e2e_run_node
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_e2e_run_node`;
+CREATE TABLE `lobster_e2e_run_node` (
+  `id`               BIGINT       NOT NULL AUTO_INCREMENT,
+  `run_id`           VARCHAR(64)  NOT NULL COMMENT '关联 lobster_e2e_run.run_id',
+  `node_seq`         INT          NOT NULL COMMENT '节点序号',
+  `node_code`        VARCHAR(64)  NOT NULL COMMENT '节点编码',
+  `node_type`        VARCHAR(32)  NULL     COMMENT '节点类型',
+  `node_name`        VARCHAR(128) NULL,
+  `turn_no`          INT          DEFAULT 1 COMMENT '单节点轮次(多轮对话)',
+  `user_input`       TEXT         NULL     COMMENT '用户输入',
+  `ai_output`        TEXT         NULL     COMMENT 'AI输出',
+  `score`            DECIMAL(5,2) NULL     COMMENT '本节点本轮评分',
+  `score_detail`     TEXT         NULL     COMMENT '维度评分JSON',
+  `duration_ms`      BIGINT       DEFAULT 0,
+  `model_used`       VARCHAR(64)  NULL     COMMENT '使用的模型',
+  `evolution_hint`   TEXT         NULL     COMMENT '进化建议草稿',
+  `passed`           TINYINT(1)   DEFAULT 1 COMMENT '0=未达标 1=达标',
+  `error_msg`        TEXT         NULL,
+  `create_time`      DATETIME     DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_run_seq` (`run_id`, `node_seq`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾E2E测试节点明细';
+
+-- ----------------------------
+-- Table structure for lobster_event_node_audit
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_event_node_audit`;
+CREATE TABLE `lobster_event_node_audit` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+  `instance_id` bigint DEFAULT NULL COMMENT '工作流实例ID',
+  `workflow_id` varchar(100) DEFAULT NULL COMMENT '工作流ID',
+  `node_key` varchar(100) DEFAULT NULL COMMENT '节点key',
+  `node_name` varchar(200) DEFAULT NULL COMMENT '节点名称',
+  `node_type` varchar(50) DEFAULT NULL COMMENT '节点类型',
+  `node_json` text COMMENT '节点完整JSON',
+  `insert_at` varchar(50) DEFAULT NULL COMMENT '插入位置(before/after)',
+  `insert_ref_node` varchar(100) DEFAULT NULL COMMENT '参考节点key',
+  `decision_engine` varchar(100) DEFAULT NULL COMMENT '决策引擎名称',
+  `decision_score` decimal(5,2) DEFAULT NULL COMMENT 'AI决策置信度',
+  `decision_reason` text COMMENT 'AI决策理由',
+  `status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '状态:pending/approved/rejected',
+  `audit_by` varchar(64) DEFAULT NULL COMMENT '审核人',
+  `audit_comment` varchar(500) DEFAULT NULL COMMENT '审核备注',
+  `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_company_status` (`company_id`,`status`),
+  KEY `idx_instance_id` (`instance_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾事件节点审核';
+
+-- ----------------------------
+-- Table structure for lobster_evolution_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_evolution_log`;
+CREATE TABLE `lobster_evolution_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint NOT NULL COMMENT '租户ID',
+  `workflow_id` bigint DEFAULT NULL COMMENT '工作流ID',
+  `instance_id` bigint DEFAULT NULL COMMENT '工作流实例ID',
+  `contact_id` bigint DEFAULT NULL COMMENT '联系人ID',
+  `channel_type` varchar(20) DEFAULT NULL COMMENT '通道类型:IM/WX/QW',
+  `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+  `sent_message` text COMMENT '发送的消息内容',
+  `customer_reply` text COMMENT '客户回复内容',
+  `outcome` varchar(50) DEFAULT NULL COMMENT '结果:replied/no_reply/converted/churned',
+  `variables` text COMMENT '工作流变量快照JSON',
+  `duration_ms` bigint DEFAULT NULL COMMENT '交互耗时(毫秒)',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_company_workflow` (`company_id`,`workflow_id`),
+  KEY `idx_company_node` (`company_id`,`node_code`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='进化引擎-交互日志表';
+
+-- ----------------------------
+-- Table structure for lobster_evolution_suggestion
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_evolution_suggestion`;
+CREATE TABLE `lobster_evolution_suggestion` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint NOT NULL COMMENT '租户ID',
+  `workflow_id` bigint DEFAULT NULL COMMENT '工作流ID',
+  `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+  `suggestion_type` varchar(50) DEFAULT NULL COMMENT '建议类型:message_optimize/timing_adjust/flow_restructure',
+  `current_content` text COMMENT '当前内容',
+  `suggested_content` text COMMENT '建议内容',
+  `confidence` double DEFAULT NULL COMMENT '置信度',
+  `reason` text COMMENT '优化原因',
+  `status` tinyint DEFAULT '0' COMMENT '状态:0-待审核,1-已应用,2-已拒绝',
+  `apply_time` datetime DEFAULT NULL COMMENT '应用时间',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_company` (`company_id`),
+  KEY `idx_status` (`company_id`,`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='进化引擎-优化建议表';
+
+-- ----------------------------
+-- Table structure for lobster_handoff_events
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_handoff_events`;
+CREATE TABLE `lobster_handoff_events` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    instance_id BIGINT DEFAULT NULL,
+    external_user_id VARCHAR(128) DEFAULT NULL,
+    trigger_type VARCHAR(50) COMMENT 'sensitive_word/sensitive_word_high_risk/semantic_takeover',
+    trigger_detail VARCHAR(500) DEFAULT NULL,
+    create_time DATETIME DEFAULT NOW(),
+    INDEX idx_company (company_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_heartbeat_registry
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_heartbeat_registry`;
+CREATE TABLE `lobster_heartbeat_registry` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `instance_id` bigint NOT NULL,
+  `company_id` bigint NOT NULL,
+  `workflow_id` bigint NOT NULL,
+  `contact_id` bigint DEFAULT NULL,
+  `channel_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'QW',
+  `interval_ms` bigint DEFAULT '300000',
+  `timeout_ms` bigint DEFAULT '86400000',
+  `max_retries` int DEFAULT '3',
+  `auto_execute` tinyint DEFAULT '1',
+  `status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'ACTIVE',
+  `last_heartbeat` datetime DEFAULT CURRENT_TIMESTAMP,
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_instance_id` (`instance_id`),
+  KEY `idx_company` (`company_id`),
+  KEY `idx_status` (`status`),
+  KEY `idx_last_heartbeat` (`last_heartbeat`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='龙虾引擎-心跳注册表';
+
+-- ----------------------------
+-- Table structure for lobster_knowledge_usage_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_knowledge_usage_log`;
+CREATE TABLE `lobster_knowledge_usage_log` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    instance_id BIGINT DEFAULT NULL,
+    knowledge_title VARCHAR(200) DEFAULT NULL,
+    knowledge_text VARCHAR(500) DEFAULT NULL,
+    retrieval_method VARCHAR(30) COMMENT 'vector/keyword',
+    source VARCHAR(50) DEFAULT 'context_assembler',
+    create_time DATETIME DEFAULT NOW(),
+    INDEX idx_company (company_id),
+    INDEX idx_company_title (company_id, knowledge_title)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_message_delivery_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_message_delivery_log`;
+CREATE TABLE `lobster_message_delivery_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint NOT NULL,
+  `instance_id` bigint DEFAULT NULL,
+  `workflow_id` bigint DEFAULT NULL,
+  `contact_id` bigint DEFAULT NULL,
+  `channel_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+  `channel_user_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
+  `msg_type` int DEFAULT '1',
+  `channel_msg_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `success` tinyint DEFAULT '0',
+  `error_msg` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_company` (`company_id`),
+  KEY `idx_instance` (`instance_id`),
+  KEY `idx_channel_type` (`channel_type`),
+  KEY `idx_create_time` (`create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='龙虾引擎-消息触达日志';
+
+-- ----------------------------
+-- Table structure for lobster_model_config
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_model_config`;
+CREATE TABLE `lobster_model_config` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `model_name` varchar(100) NOT NULL,
+  `model_key` varchar(100) NOT NULL,
+  `provider` varchar(100) DEFAULT NULL,
+  `api_endpoint` varchar(500) DEFAULT NULL,
+  `max_tokens` int DEFAULT '4096',
+  `temperature` decimal(3,2) DEFAULT '0.70',
+  `enabled` tinyint DEFAULT '1',
+  `token_coefficient` decimal(10,4) DEFAULT '1.0000',
+  `company_id` bigint DEFAULT NULL,
+  `create_by` varchar(64) DEFAULT '',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_by` varchar(64) DEFAULT '',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_model_key` (`model_key`),
+  KEY `idx_company` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='模型配置';
+
+-- ----------------------------
+-- Table structure for lobster_multi_turn_dialogue
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_multi_turn_dialogue`;
+CREATE TABLE `lobster_multi_turn_dialogue` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT DEFAULT NULL,
+    instance_id BIGINT NOT NULL,
+    node_code VARCHAR(100) NOT NULL,
+    state_json TEXT,
+    turn_index INT DEFAULT 0,
+    direction INT COMMENT '1=客户 2=AI',
+    content TEXT,
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_instance_node (instance_id, node_code),
+    INDEX idx_company (company_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_node_execution_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_node_execution_log`;
+CREATE TABLE `lobster_node_execution_log` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL,
+  `instance_id` bigint DEFAULT NULL,
+  `workflow_id` bigint DEFAULT NULL,
+  `node_index` int DEFAULT NULL,
+  `node_type` varchar(50) DEFAULT NULL,
+  `node_name` varchar(100) DEFAULT NULL,
+  `input_content` text,
+  `output_content` text,
+  `status` varchar(50) DEFAULT NULL,
+  `retry_count` int DEFAULT '0',
+  `del_flag` int DEFAULT '0',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  `ai_model` varchar(100) DEFAULT NULL,
+  `duration_ms` bigint DEFAULT NULL,
+  `token_usage` int DEFAULT NULL,
+  `error_message` text,
+  PRIMARY KEY (`id`),
+  KEY `idx_instance_id` (`instance_id`),
+  KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB AUTO_INCREMENT=57 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- ----------------------------
+-- Table structure for lobster_pay_order
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_pay_order`;
+CREATE TABLE `lobster_pay_order` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    contact_id BIGINT DEFAULT NULL,
+    instance_id BIGINT DEFAULT NULL,
+    order_no VARCHAR(64) NOT NULL,
+    product_name VARCHAR(256),
+    amount BIGINT DEFAULT 0 COMMENT '金额(分)',
+    status VARCHAR(20) DEFAULT 'CREATED',
+    gateway VARCHAR(20) DEFAULT 'wechat',
+    transaction_id VARCHAR(128) DEFAULT NULL,
+    extra TEXT,
+    create_time DATETIME DEFAULT NOW(),
+    paid_time DATETIME DEFAULT NULL,
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_order (order_no),
+    INDEX idx_company_status (company_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_pending_knowledge
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_pending_knowledge`;
+CREATE TABLE `lobster_pending_knowledge` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    external_user_id VARCHAR(128) DEFAULT NULL,
+    knowledge_type VARCHAR(50) DEFAULT 'auto',
+    content TEXT,
+    context_snapshot TEXT COMMENT '评分维度/上下文快照JSON',
+    source_node_code VARCHAR(100) DEFAULT NULL,
+    status VARCHAR(30) DEFAULT 'pending' COMMENT 'pending/APPROVED/REJECTED',
+    auditor_id BIGINT DEFAULT NULL,
+    audit_comment VARCHAR(500) DEFAULT NULL,
+    audit_time DATETIME DEFAULT NULL,
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    INDEX idx_company_status (company_id, status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_sales_corpus
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_sales_corpus`;
+CREATE TABLE `lobster_sales_corpus` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `dialog_text` text,
+  `scenario` varchar(100) DEFAULT NULL,
+  `score` decimal(5,2) DEFAULT NULL,
+  `source_type` varchar(50) DEFAULT NULL,
+  `external_user_id` varchar(100) DEFAULT NULL,
+  `company_id` bigint DEFAULT NULL,
+  `tags` varchar(500) DEFAULT NULL,
+  `enabled` tinyint DEFAULT '1',
+  `create_by` varchar(64) DEFAULT '',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_by` varchar(64) DEFAULT '',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_scenario` (`scenario`),
+  KEY `idx_company` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='销冠语料库';
+
+-- ----------------------------
+-- Table structure for lobster_sensitive_word
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_sensitive_word`;
+CREATE TABLE `lobster_sensitive_word` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    word VARCHAR(200) NOT NULL,
+    category VARCHAR(50) DEFAULT '通用',
+    level VARCHAR(20) DEFAULT 'warn',
+    replacement VARCHAR(200) DEFAULT '***',
+    enabled INT DEFAULT 1,
+    create_time DATETIME DEFAULT NOW(),
+    create_by VARCHAR(64) DEFAULT 'SYSTEM',
+    INDEX idx_company (company_id),
+    UNIQUE KEY uk_word (company_id, word)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_smart_api
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_smart_api`;
+CREATE TABLE `lobster_smart_api` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT DEFAULT NULL,
+    api_code VARCHAR(100) NOT NULL COMMENT 'API唯一标识',
+    api_name VARCHAR(200) DEFAULT NULL,
+    api_url VARCHAR(500) NOT NULL,
+    api_method VARCHAR(10) DEFAULT 'POST',
+    headers_json VARCHAR(1000) DEFAULT '{}',
+    body_template TEXT,
+    description VARCHAR(500) DEFAULT NULL,
+    enabled INT DEFAULT 1,
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_code (company_id, api_code)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_system_prompt
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_system_prompt`;
+CREATE TABLE `lobster_system_prompt` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `prompt_key` varchar(100) NOT NULL COMMENT '唯一键',
+  `prompt_name` varchar(200) DEFAULT NULL COMMENT '提示词名称',
+  `prompt_category` varchar(50) DEFAULT NULL COMMENT '分类: rag/ai/code_collect/handoff/kb',
+  `prompt_content` text NOT NULL COMMENT '提示词内容',
+  `model_name` varchar(100) DEFAULT 'doubao-lite' COMMENT '推荐模型',
+  `system_role` varchar(500) DEFAULT NULL COMMENT 'system角色提示',
+  `enabled` int DEFAULT '1' COMMENT '1启用0禁用',
+  `sort_order` int DEFAULT '0',
+  `create_by` varchar(64) DEFAULT 'admin',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `company_id` bigint DEFAULT NULL COMMENT '租户ID,NULL=系统默认',
+  `industry_type` varchar(50) DEFAULT NULL COMMENT '行业类型: travel/medical/education/insurance/general,NULL=通用',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `prompt_key` (`prompt_key`),
+  KEY `idx_category` (`prompt_category`),
+  KEY `idx_enabled` (`enabled`),
+  KEY `idx_company_industry` (`company_id`,`industry_type`)
+) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- ----------------------------
+-- Table structure for lobster_test_scenario
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_test_scenario`;
+CREATE TABLE `lobster_test_scenario` (
+  `id`                 BIGINT       NOT NULL AUTO_INCREMENT,
+  `company_id`         BIGINT       NULL,
+  `scenario_name`      VARCHAR(128) NOT NULL COMMENT '场景名',
+  `business_desc`      VARCHAR(500) NULL     COMMENT '业务描述(留空则用 template_id)',
+  `template_id`        BIGINT       NULL     COMMENT '关联工作流模板',
+  `user_inputs_json`   TEXT         NOT NULL COMMENT '用户输入数组JSON',
+  `expected_nodes`     TEXT         NULL     COMMENT '期望命中的节点编码JSON',
+  `min_score`          DECIMAL(5,2) DEFAULT 60.00 COMMENT '最低通过分',
+  `enabled`            TINYINT(1)   DEFAULT 1 COMMENT '是否启用回归',
+  `cron`               VARCHAR(64)  NULL     COMMENT '自定义cron(空则跟随全局)',
+  `last_run_id`        VARCHAR(64)  NULL,
+  `last_run_status`    VARCHAR(20)  NULL,
+  `last_run_time`      DATETIME     NULL,
+  `create_by`          VARCHAR(64)  NULL,
+  `create_time`        DATETIME     DEFAULT CURRENT_TIMESTAMP,
+  `update_time`        DATETIME     DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_enabled` (`enabled`),
+  KEY `idx_company` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾测试场景剧本';
+
+-- ----------------------------
+-- Table structure for lobster_unified_contact
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_unified_contact`;
+CREATE TABLE `lobster_unified_contact` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint NOT NULL,
+  `contact_id` bigint NOT NULL,
+  `contact_name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `contact_phone` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `channel_type` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
+  `channel_user_id` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
+  `extra` json DEFAULT NULL,
+  `del_flag` tinyint DEFAULT '0',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_contact_channel` (`company_id`,`contact_id`,`channel_type`),
+  KEY `idx_company` (`company_id`),
+  KEY `idx_channel_type` (`channel_type`),
+  KEY `idx_channel_user` (`channel_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='龙虾引擎-统一联系人';
+
+-- ----------------------------
+-- Table structure for lobster_user_profile
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_user_profile`;
+CREATE TABLE `lobster_user_profile` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    company_id BIGINT NOT NULL,
+    external_user_id VARCHAR(128) NOT NULL,
+    nickname VARCHAR(128) DEFAULT NULL,
+    persona VARCHAR(500) DEFAULT NULL,
+    current_state VARCHAR(100) DEFAULT NULL,
+    lifecycle_stage VARCHAR(30) DEFAULT 'NEW' COMMENT 'NEW/ACTIVE/DORMANT/SLEEP/CHURN',
+    value_score INT DEFAULT 10,
+    total_purchase DECIMAL(12,2) DEFAULT 0,
+    interaction_count INT DEFAULT 0,
+    last_active_time DATETIME DEFAULT NULL,
+    internal_tags VARCHAR(1000) DEFAULT NULL,
+    variable_snapshot VARCHAR(2000) DEFAULT NULL,
+    deleted INT DEFAULT 0,
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_user (company_id, external_user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for lobster_workflow_instance
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_workflow_instance`;
+CREATE TABLE `lobster_workflow_instance` (
+  `id` bigint NOT NULL AUTO_INCREMENT,
+  `company_id` bigint DEFAULT NULL,
+  `workflow_id` bigint DEFAULT NULL,
+  `instance_name` varchar(200) DEFAULT NULL,
+  `status` varchar(20) DEFAULT 'pending',
+  `contact_id` bigint DEFAULT NULL,
+  `contact_name` varchar(100) DEFAULT NULL,
+  `channel_type` varchar(20) DEFAULT 'QW',
+  `current_node_index` int DEFAULT '0',
+  `current_node_name` varchar(100) DEFAULT NULL,
+  `total_nodes` int DEFAULT '0',
+  `completed_nodes` int DEFAULT '0',
+  `context_snapshot` text,
+  `variables` text,
+  `start_time` varchar(30) DEFAULT NULL,
+  `end_time` varchar(30) DEFAULT NULL,
+  `last_activity_time` varchar(30) DEFAULT NULL,
+  `error_message` text,
+  `del_flag` int DEFAULT '0',
+  `create_by` varchar(64) DEFAULT NULL,
+  `create_time` datetime DEFAULT NULL,
+  `update_by` varchar(64) DEFAULT NULL,
+  `update_time` datetime DEFAULT NULL,
+  `handoff_agent` varchar(100) DEFAULT NULL COMMENT '转人工目标',
+  `handoff_reason` varchar(500) DEFAULT NULL COMMENT '转人工原因',
+  `control_mode` varchar(20) DEFAULT 'ai' COMMENT '控制模式: ai=龙虾接管, human=人工接管',
+  `control_updated_by` varchar(64) DEFAULT NULL COMMENT '切换操作人',
+  `control_updated_at` datetime DEFAULT NULL COMMENT '切换时间',
+  PRIMARY KEY (`id`),
+  KEY `idx_company_id` (`company_id`),
+  KEY `idx_workflow_id` (`workflow_id`),
+  KEY `idx_contact_id` (`contact_id`),
+  KEY `idx_status` (`status`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+-- ----------------------------
+-- Table structure for lobster_workflow_node_type
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_workflow_node_type`;
+CREATE TABLE `lobster_workflow_node_type` (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT,
+    node_type INT NOT NULL UNIQUE COMMENT '节点编号(与执行器常量对齐)',
+    node_name VARCHAR(64) NOT NULL COMMENT '节点显示名称',
+    code_name VARCHAR(64) NOT NULL COMMENT '节点代码标识',
+    description VARCHAR(255) COMMENT '节点描述 + skill.md 别名',
+    category VARCHAR(32) COMMENT '分类:basic/flow/business/api',
+    icon VARCHAR(64) COMMENT '图标 class',
+    color VARCHAR(16) COMMENT '主题色 hex',
+    template_json TEXT COMMENT '默认配置模板 JSON',
+    enabled TINYINT DEFAULT 1,
+    sort_order INT DEFAULT 0,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='龙虾工作流节点类型字典';
+
+-- ----------------------------
+-- Table structure for lobster_workflow_variable
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_workflow_variable`;
+CREATE TABLE `lobster_workflow_variable` (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    instance_id BIGINT NOT NULL,
+    company_id BIGINT DEFAULT NULL,
+    var_key VARCHAR(200) NOT NULL,
+    var_value TEXT,
+    create_time DATETIME DEFAULT NOW(),
+    update_time DATETIME DEFAULT NOW(),
+    UNIQUE KEY uk_inst_key (instance_id, var_key)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for outbound_line_limit_log
+-- ----------------------------
+DROP TABLE IF EXISTS `outbound_line_limit_log`;
+CREATE TABLE `outbound_line_limit_log` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `gateway_id` bigint DEFAULT NULL COMMENT '网关ID',
+    `time_window` int DEFAULT NULL COMMENT '时间粒度(分钟)',
+    `window_start` datetime DEFAULT NULL COMMENT '时间窗口起始',
+    `max_calls` int DEFAULT NULL COMMENT '窗口最大呼出次数',
+    `current_count` int DEFAULT NULL COMMENT '当前已呼叫次数',
+    `is_limited` tinyint DEFAULT 0 COMMENT '是否触发限制 0否1是',
+    `call_param` text COMMENT '触发时调用参数JSON',
+    `next_available_time` datetime DEFAULT NULL COMMENT '下一可用时间',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_company_gateway_window` (`company_id`, `gateway_id`, `time_window`, `create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='外呼线路限流日志';
+
+-- ----------------------------
+-- Table structure for qw_customer_property
+-- ----------------------------
+DROP TABLE IF EXISTS `qw_customer_property`;
+CREATE TABLE `qw_customer_property` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
+  `external_user_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户ID',
+  `property_id` bigint DEFAULT NULL COMMENT '字段ID',
+  `property_name` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段名称',
+  `property_value` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段内容',
+  `property_value_type` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段类型',
+  `trade_type` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '行业类型',
+  `ai_analysis` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '内容解析',
+  `intention` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '意向登记:high-高意向,medium-中意向,low-低意向,none-无意向',
+  `like_ratio` int DEFAULT NULL COMMENT '喜欢占比:0-100',
+  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `create_by` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',
+  `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
+  `update_by` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '修改人',
+  `remark` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
+  `deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除 0否 1是',
+  `delete_by` varchar(300) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '删除人',
+  `delete_time` datetime DEFAULT NULL COMMENT '删除时间',
+  `corp_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '企业id',
+  `qw_user_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '属于用户id',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `qw_id_index` (`external_user_id`,`corp_id`,`qw_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+
+-- ----------------------------
+-- Table structure for qw_external_ai_analyze
+-- ----------------------------
+DROP TABLE IF EXISTS `qw_external_ai_analyze`;
+CREATE TABLE `qw_external_ai_analyze` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
+  `external_user_id` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '外部联系人id',
+  `customer_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户姓名',
+  `customer_portrait_json` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户画像',
+  `communication_abstract` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '沟通摘要',
+  `communication_summary` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '沟通总结',
+  `attrition_level` tinyint DEFAULT '0' COMMENT '流失风险等级 0:未知;1:无风险;2:低风险;3:中风险;4:高风险',
+  `attrition_level_prompt` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '流失风险等级提示',
+  `customer_focus_json` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '客户关注点',
+  `intention_degree` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '意向度',
+  `ai_chat_record` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT 'ai通话聊天记录',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '备注',
+  `reserve_int` bigint DEFAULT '0' COMMENT '预留数字型字段',
+  `reserve_str` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '预留字符串型字段',
+  `corp_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '企业id',
+  `qw_user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '属于用户id',
+  `session_id` bigint DEFAULT NULL COMMENT 'qw_external_ai_analyze_session的iD,ai会话',
+  PRIMARY KEY (`id`) USING BTREE,
+  KEY `idx_customer_id` (`external_user_id`) USING BTREE,
+  KEY `idx_customer_time_id` (`external_user_id`,`create_time`,`id`) USING BTREE,
+  KEY `indx_user_bind` (`external_user_id`,`corp_id`,`qw_user_id`) USING BTREE COMMENT '用户关系唯一绑定',
+  FULLTEXT KEY `idx_ai_chat_record_fulltext` (`ai_chat_record`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;
+
+
+-- ============================================================================
+-- tenant-initTable 补全:Mapper/实体引用且 patch 无 DDL 的表
+-- ============================================================================
+
+-- ----------------------------
+-- Table structure for ai_chat_quality_record
+-- ----------------------------
+DROP TABLE IF EXISTS `ai_chat_quality_record`;
+CREATE TABLE `ai_chat_quality_record` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `session_id` varchar(64) DEFAULT NULL COMMENT '会话ID',
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `company_name` varchar(200) DEFAULT NULL COMMENT '公司名称',
+    `user_id` varchar(64) DEFAULT NULL COMMENT '用户ID',
+    `user_name` varchar(200) DEFAULT NULL COMMENT '用户名',
+    `content_summary` text COMMENT '内容摘要',
+    `quality_result` varchar(50) DEFAULT NULL COMMENT '质检结果',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    `quality_user_id` bigint DEFAULT NULL COMMENT '质检人ID',
+    `quality_user_name` varchar(200) DEFAULT NULL COMMENT '质检人',
+    `quality_time` datetime DEFAULT NULL COMMENT '质检时间',
+    `risk_level` varchar(20) DEFAULT NULL COMMENT '风险等级',
+    `sensitive_words` varchar(500) DEFAULT NULL COMMENT '敏感词',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`),
+    KEY `idx_session_id` (`session_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI聊天质检记录';
+
+-- ----------------------------
+-- Table structure for cc_ext_num
+-- ----------------------------
+DROP TABLE IF EXISTS `cc_ext_num`;
+CREATE TABLE `cc_ext_num` (
+    `ext_id` bigint NOT NULL AUTO_INCREMENT,
+    `ext_num` varchar(50) NOT NULL COMMENT '分机号',
+    `ext_pass` varchar(100) DEFAULT NULL COMMENT '分机密码',
+    `user_code` varchar(100) DEFAULT NULL COMMENT '用户编码',
+    PRIMARY KEY (`ext_id`),
+    UNIQUE KEY `uk_ext_num` (`ext_num`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI外呼分机号';
+
+-- ----------------------------
+-- Table structure for company_knowledge_base
+-- ----------------------------
+DROP TABLE IF EXISTS `company_knowledge_base`;
+CREATE TABLE `company_knowledge_base` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `base_id` bigint DEFAULT NULL COMMENT '知识库ID',
+    `title` varchar(200) DEFAULT NULL COMMENT '标题',
+    `question` text COMMENT '问题',
+    `answer` text COMMENT '答案',
+    `industry_type` varchar(50) DEFAULT NULL COMMENT '行业类型',
+    `source` varchar(50) DEFAULT NULL COMMENT '来源',
+    `audit_status` tinyint DEFAULT 0 COMMENT '审核状态',
+    `audit_comment` varchar(500) DEFAULT NULL COMMENT '审核意见',
+    `auditor` varchar(64) DEFAULT NULL COMMENT '审核人',
+    `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+    `use_count` int DEFAULT 0 COMMENT '使用次数',
+    `fastgpt_id` varchar(64) DEFAULT NULL COMMENT 'FastGPT ID',
+    `sync_status` tinyint DEFAULT 0 COMMENT '同步状态',
+    `sync_time` datetime DEFAULT NULL COMMENT '同步时间',
+    `del_flag` tinyint DEFAULT 0 COMMENT '删除标记',
+    `create_by` varchar(64) DEFAULT NULL,
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_by` varchar(64) DEFAULT NULL,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`),
+    KEY `idx_base_id` (`base_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='公司知识库';
+
+-- ----------------------------
+-- Table structure for company_knowledge_audit
+-- ----------------------------
+DROP TABLE IF EXISTS `company_knowledge_audit`;
+CREATE TABLE `company_knowledge_audit` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `knowledge_id` bigint DEFAULT NULL COMMENT '知识ID',
+    `source_type` varchar(50) DEFAULT NULL COMMENT '来源类型',
+    `source_id` bigint DEFAULT NULL COMMENT '来源ID',
+    `content` text COMMENT '内容',
+    `suggestion` text COMMENT '建议',
+    `audit_result` varchar(50) DEFAULT NULL COMMENT '审核结果',
+    `audit_comment` varchar(500) DEFAULT NULL COMMENT '审核意见',
+    `auditor` varchar(64) DEFAULT NULL COMMENT '审核人',
+    `audit_time` datetime DEFAULT NULL COMMENT '审核时间',
+    `del_flag` tinyint DEFAULT 0 COMMENT '删除标记',
+    `create_by` varchar(64) DEFAULT NULL,
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_by` varchar(64) DEFAULT NULL,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='公司知识审核';
+
+-- ----------------------------
+-- Table structure for company_knowledge_suggestion
+-- ----------------------------
+DROP TABLE IF EXISTS `company_knowledge_suggestion`;
+CREATE TABLE `company_knowledge_suggestion` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `type` varchar(50) DEFAULT NULL COMMENT '建议类型',
+    `description` text COMMENT '描述',
+    `suggestion` text COMMENT '建议内容',
+    `impact` varchar(200) DEFAULT NULL COMMENT '影响',
+    `status` varchar(30) DEFAULT 'pending' COMMENT '状态',
+    `apply_time` datetime DEFAULT NULL COMMENT '应用时间',
+    `del_flag` tinyint DEFAULT 0 COMMENT '删除标记',
+    `create_by` varchar(64) DEFAULT NULL,
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_by` varchar(64) DEFAULT NULL,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='公司知识建议';
+
+-- ----------------------------
+-- Table structure for company_lobster_dedup_config
+-- ----------------------------
+DROP TABLE IF EXISTS `company_lobster_dedup_config`;
+CREATE TABLE `company_lobster_dedup_config` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `config_name` varchar(100) DEFAULT NULL COMMENT '配置名称',
+    `dedup_mode` varchar(30) DEFAULT NULL COMMENT '去重模式',
+    `exact_window_size` int DEFAULT NULL COMMENT '精确窗口大小',
+    `semantic_threshold` double DEFAULT NULL COMMENT '语义阈值',
+    `window_duration_seconds` int DEFAULT NULL COMMENT '窗口时长(秒)',
+    `ignore_prefix_count` int DEFAULT NULL COMMENT '忽略前缀数',
+    `enabled` tinyint DEFAULT 1 COMMENT '是否启用',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾消息去重配置';
+
+-- ----------------------------
+-- Table structure for company_sms_api
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_api`;
+CREATE TABLE `company_sms_api` (
+    `api_id` bigint NOT NULL AUTO_INCREMENT COMMENT '接口ID',
+    `api_name` varchar(100) DEFAULT NULL COMMENT '接口名称',
+    `provider` varchar(50) DEFAULT NULL COMMENT '服务商',
+    `sms_type` tinyint DEFAULT NULL COMMENT '短信类型',
+    `account` varchar(100) DEFAULT NULL COMMENT '账户名',
+    `password` varchar(200) DEFAULT NULL COMMENT '密码',
+    `url` varchar(500) DEFAULT NULL COMMENT '接口地址',
+    `callback_url` varchar(500) DEFAULT NULL COMMENT '回调地址',
+    `code` varchar(50) DEFAULT NULL COMMENT '扩展码',
+    `sign` varchar(50) DEFAULT NULL COMMENT '签名',
+    `cost_price` decimal(10,4) DEFAULT NULL COMMENT '成本价',
+    `is_default` tinyint DEFAULT 0 COMMENT '是否默认',
+    `status` tinyint DEFAULT 1 COMMENT '状态',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`api_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='短信接口配置';
+
+-- ----------------------------
+-- Table structure for company_sms_api_port
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_api_port`;
+CREATE TABLE `company_sms_api_port` (
+    `port_id` bigint NOT NULL AUTO_INCREMENT COMMENT '端口ID',
+    `api_id` bigint NOT NULL COMMENT '接口ID',
+    `port_name` varchar(100) DEFAULT NULL COMMENT '端口名称',
+    `port_no` varchar(50) DEFAULT NULL COMMENT '端口号',
+    `account` varchar(100) DEFAULT NULL COMMENT '账户',
+    `password` varchar(200) DEFAULT NULL COMMENT '密码',
+    `sign` varchar(50) DEFAULT NULL COMMENT '签名',
+    `slot_index` tinyint DEFAULT 1 COMMENT '卡槽',
+    `status` tinyint DEFAULT 1 COMMENT '状态',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`port_id`),
+    KEY `idx_api_id` (`api_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='短信接口端口';
+
+-- ----------------------------
+-- Table structure for company_sms_card
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_card`;
+CREATE TABLE `company_sms_card` (
+    `card_id` bigint NOT NULL AUTO_INCREMENT COMMENT '卡ID',
+    `port_id` bigint DEFAULT NULL COMMENT '端口ID',
+    `tenant_id` bigint NOT NULL COMMENT '租户ID',
+    `device_id` bigint DEFAULT NULL COMMENT '设备ID',
+    `slot_index` tinyint DEFAULT 1 COMMENT '卡槽(1或2)',
+    `imei` varchar(50) DEFAULT NULL COMMENT 'IMEI',
+    `device_name` varchar(100) DEFAULT NULL COMMENT '设备名称',
+    `sim_count` tinyint DEFAULT 1 COMMENT '卡槽数',
+    `phone_1` varchar(20) DEFAULT NULL COMMENT '卡槽1手机号',
+    `phone_2` varchar(20) DEFAULT NULL COMMENT '卡槽2手机号',
+    `last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳',
+    `status` tinyint DEFAULT 0 COMMENT '0离线/1在线/2禁用',
+    `app_version` varchar(30) DEFAULT NULL COMMENT 'APP版本',
+    `sms_sent_today` int DEFAULT 0 COMMENT '今日已发短信',
+    `sms_sent_date` date DEFAULT NULL COMMENT '短信计数日期',
+    `sms_sent_hour` int DEFAULT 0 COMMENT '当前小时已发',
+    `sms_sent_hour_num` tinyint DEFAULT NULL COMMENT '当前小时(0-23)',
+    `sms_hourly_limit` int DEFAULT NULL COMMENT '每小时限制',
+    `sms_daily_limit` int DEFAULT NULL COMMENT '每日限制',
+    `sms_balance` int DEFAULT NULL COMMENT '短信余额',
+    `call_sent_today` int DEFAULT 0 COMMENT '今日已拨号',
+    `call_sent_date` date DEFAULT NULL COMMENT '拨号计数日期',
+    `call_interval_seconds` int DEFAULT NULL COMMENT '拨打间隔(秒)',
+    `call_minutes_balance` decimal(10,2) DEFAULT NULL COMMENT '通话分钟余额',
+    `phone_bill_balance` decimal(10,2) DEFAULT NULL COMMENT '话费余额',
+    `allow_call_forward` tinyint DEFAULT 0 COMMENT '允许呼转',
+    `forward_phone` varchar(20) DEFAULT NULL COMMENT '呼转号码',
+    `last_call_time` datetime DEFAULT NULL COMMENT '最后拨号时间',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`card_id`),
+    KEY `idx_tenant_id` (`tenant_id`),
+    KEY `idx_device_id` (`device_id`),
+    KEY `idx_imei` (`imei`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='手机卡管理';
+
+-- ----------------------------
+-- Table structure for company_sms_card_middleware
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_card_middleware`;
+CREATE TABLE `company_sms_card_middleware` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `api_id` bigint DEFAULT NULL COMMENT '接口ID',
+    `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
+    `middleware_name` varchar(100) DEFAULT NULL COMMENT '中间件名称',
+    `callback_url` varchar(500) DEFAULT NULL COMMENT '回调地址',
+    `heartbeat_url` varchar(500) DEFAULT NULL COMMENT '心跳地址',
+    `auth_token` varchar(200) DEFAULT NULL COMMENT '认证Token',
+    `max_retry` int DEFAULT 3 COMMENT '最大重试',
+    `timeout_seconds` int DEFAULT 30 COMMENT '超时秒数',
+    `status` tinyint DEFAULT 1 COMMENT '状态',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_api_id` (`api_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='手机卡中间件';
+
+-- ----------------------------
+-- Table structure for company_sms_device
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_device`;
+CREATE TABLE `company_sms_device` (
+    `device_id` bigint NOT NULL AUTO_INCREMENT COMMENT '设备ID',
+    `tenant_id` bigint NOT NULL COMMENT '所属租户',
+    `company_user_id` bigint DEFAULT NULL COMMENT '绑定销售用户ID',
+    `device_name` varchar(100) DEFAULT NULL COMMENT '设备名称',
+    `imei` varchar(50) NOT NULL COMMENT 'IMEI',
+    `app_version` varchar(30) DEFAULT NULL COMMENT 'APP版本',
+    `middleware_id` bigint DEFAULT NULL COMMENT '关联中间件ID',
+    `last_heartbeat` datetime DEFAULT NULL COMMENT '最后心跳时间',
+    `status` tinyint DEFAULT 0 COMMENT '0离线/1在线/2禁用',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`device_id`),
+    UNIQUE KEY `uk_imei` (`imei`),
+    KEY `idx_tenant_id` (`tenant_id`),
+    KEY `idx_company_user_id` (`company_user_id`),
+    KEY `idx_middleware_id` (`middleware_id`),
+    KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='手机设备管理';
+
+-- ----------------------------
+-- Table structure for company_sms_port_assign
+-- ----------------------------
+DROP TABLE IF EXISTS `company_sms_port_assign`;
+CREATE TABLE `company_sms_port_assign` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `port_id` bigint NOT NULL COMMENT '端口ID',
+    `tenant_id` bigint NOT NULL COMMENT '租户ID',
+    `company_user_id` bigint DEFAULT NULL COMMENT '销售用户ID',
+    `status` tinyint DEFAULT 1 COMMENT '状态',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_port_id` (`port_id`),
+    KEY `idx_tenant_id` (`tenant_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='短信端口分配';
+
+-- ----------------------------
+-- Table structure for im_send_log
+-- ----------------------------
+DROP TABLE IF EXISTS `im_send_log`;
+CREATE TABLE `im_send_log` (
+    `log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '日志ID',
+    `send_id` varchar(64) DEFAULT NULL COMMENT '发送方ID',
+    `recv_id` varchar(64) DEFAULT NULL COMMENT '接收方ID',
+    `send_title` varchar(200) DEFAULT NULL COMMENT '标题',
+    `plan_send_time` datetime DEFAULT NULL COMMENT '计划发送时间',
+    `actual_send_time` datetime DEFAULT NULL COMMENT '实际发送时间',
+    `send_type` tinyint DEFAULT NULL COMMENT '发送类型',
+    `param_json` text COMMENT '参数JSON',
+    `status` tinyint DEFAULT NULL COMMENT '状态',
+    `result_message` varchar(500) DEFAULT NULL COMMENT '结果信息',
+    `exception_info` text COMMENT '异常信息',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    PRIMARY KEY (`log_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='IM发送日志';
+
+-- ----------------------------
+-- Table structure for lobster_e2e_test
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_e2e_test`;
+CREATE TABLE `lobster_e2e_test` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `test_name` varchar(128) DEFAULT NULL COMMENT '测试名称',
+    `workflow_id` bigint DEFAULT NULL COMMENT '工作流ID',
+    `test_data` text COMMENT '测试数据JSON',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾E2E测试(旧)';
+
+-- ----------------------------
+-- Table structure for lobster_e2e_test_result
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_e2e_test_result`;
+CREATE TABLE `lobster_e2e_test_result` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `test_id` bigint DEFAULT NULL COMMENT '测试ID',
+    `run_id` varchar(64) DEFAULT NULL COMMENT '运行ID',
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `passed` tinyint DEFAULT NULL COMMENT '是否通过',
+    `detail` text COMMENT '详情',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_test_id` (`test_id`),
+    KEY `idx_run_id` (`run_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾E2E测试结果(旧)';
+
+-- ----------------------------
+-- Table structure for lobster_feedback_records
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_feedback_records`;
+CREATE TABLE `lobster_feedback_records` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `instance_id` bigint DEFAULT NULL COMMENT '实例ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `feedback_type` varchar(50) DEFAULT NULL COMMENT '反馈类型',
+    `comment` text COMMENT '反馈内容',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_node` (`company_id`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾反馈记录';
+
+-- ----------------------------
+-- Table structure for lobster_heartbeat
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_heartbeat`;
+CREATE TABLE `lobster_heartbeat` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `task_key` varchar(100) NOT NULL COMMENT '任务Key',
+    `status` varchar(30) DEFAULT NULL COMMENT '状态',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_task` (`company_id`, `task_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾任务心跳';
+
+-- ----------------------------
+-- Table structure for lobster_knowledge_version
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_knowledge_version`;
+CREATE TABLE `lobster_knowledge_version` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `knowledge_id` bigint NOT NULL COMMENT '知识ID',
+    `version` int NOT NULL COMMENT '版本号',
+    `title` varchar(200) DEFAULT NULL COMMENT '标题',
+    `content` text COMMENT '内容',
+    `change_log` varchar(500) DEFAULT NULL COMMENT '变更说明',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_knowledge` (`company_id`, `knowledge_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾知识版本';
+
+-- ----------------------------
+-- Table structure for lobster_learned_pattern
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_learned_pattern`;
+CREATE TABLE `lobster_learned_pattern` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `pattern_type` varchar(50) DEFAULT NULL COMMENT '模式类型',
+    `pattern_key` varchar(200) NOT NULL COMMENT '模式Key',
+    `pattern_value` text COMMENT '模式值',
+    `confidence` double DEFAULT 0.5 COMMENT '置信度',
+    `source` varchar(100) DEFAULT NULL COMMENT '来源',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_company_pattern` (`company_id`, `pattern_type`, `pattern_key`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾学习模式';
+
+-- ----------------------------
+-- Table structure for lobster_learning_event_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_learning_event_log`;
+CREATE TABLE `lobster_learning_event_log` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `instance_id` bigint DEFAULT NULL COMMENT '实例ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `event_type` varchar(50) DEFAULT NULL COMMENT '事件类型',
+    `quality_score` double DEFAULT NULL COMMENT '质量分',
+    `context_snapshot` text COMMENT '上下文快照',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_event` (`company_id`, `event_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾学习事件日志';
+
+-- ----------------------------
+-- Table structure for lobster_learning_replay_buffer
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_learning_replay_buffer`;
+CREATE TABLE `lobster_learning_replay_buffer` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `instance_id` bigint DEFAULT NULL COMMENT '实例ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `customer_message` text COMMENT '客户消息',
+    `ai_reply` text COMMENT 'AI回复',
+    `quality_score` double DEFAULT NULL COMMENT '质量分',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾学习回放缓冲';
+
+-- ----------------------------
+-- Table structure for lobster_message_variants
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_message_variants`;
+CREATE TABLE `lobster_message_variants` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `content` text COMMENT '变体内容',
+    `generation_reason` varchar(500) DEFAULT NULL COMMENT '生成原因',
+    `status` varchar(30) DEFAULT 'active' COMMENT '状态',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_node` (`company_id`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾消息变体';
+
+-- ----------------------------
+-- Table structure for lobster_node_interaction
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_node_interaction`;
+CREATE TABLE `lobster_node_interaction` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `instance_id` bigint DEFAULT NULL COMMENT '实例ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `external_user_id` varchar(128) DEFAULT NULL COMMENT '外部用户ID',
+    `interaction_type` varchar(50) DEFAULT NULL COMMENT '交互类型',
+    `content` text COMMENT '内容',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_user_node` (`company_id`, `external_user_id`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾节点交互';
+
+-- ----------------------------
+-- Table structure for lobster_optimization_config
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_optimization_config`;
+CREATE TABLE `lobster_optimization_config` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `workflow_id` bigint NOT NULL COMMENT '工作流ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `enabled` tinyint DEFAULT 1 COMMENT '是否启用',
+    `auto_apply` tinyint DEFAULT 0 COMMENT '自动应用',
+    `config_json` text COMMENT '配置JSON',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_company_workflow_node` (`company_id`, `workflow_id`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾优化配置';
+
+-- ----------------------------
+-- Table structure for lobster_prompt_config
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_prompt_config`;
+CREATE TABLE `lobster_prompt_config` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `workflow_code` varchar(100) DEFAULT NULL COMMENT '工作流编码',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `prompt_type` varchar(50) DEFAULT NULL COMMENT 'Prompt类型',
+    `content` text COMMENT '内容',
+    `scope` varchar(50) DEFAULT NULL COMMENT '作用域',
+    `deleted` tinyint DEFAULT 0 COMMENT '删除标记',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_workflow_node` (`company_id`, `workflow_code`, `node_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾Prompt配置';
+
+-- ----------------------------
+-- Table structure for lobster_sms_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_sms_log`;
+CREATE TABLE `lobster_sms_log` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
+    `content` text COMMENT '短信内容',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾短信日志';
+
+-- ----------------------------
+-- Table structure for lobster_test_scenario_result
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_test_scenario_result`;
+CREATE TABLE `lobster_test_scenario_result` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `scenario_id` bigint DEFAULT NULL COMMENT '场景ID',
+    `passed` tinyint DEFAULT NULL COMMENT '是否通过',
+    `detail` text COMMENT '详情',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_scenario_id` (`scenario_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾测试场景结果';
+
+-- ----------------------------
+-- Table structure for lobster_token_consumption
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_token_consumption`;
+CREATE TABLE `lobster_token_consumption` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `instance_id` bigint DEFAULT NULL COMMENT '实例ID',
+    `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码',
+    `model_identifier` varchar(200) DEFAULT NULL COMMENT '模型标识',
+    `consume_type` varchar(50) DEFAULT NULL COMMENT '消耗类型',
+    `token_count` bigint DEFAULT 0 COMMENT 'Token数',
+    `estimated_cost` decimal(18,6) DEFAULT NULL COMMENT '预估成本',
+    `request_time` datetime DEFAULT NULL COMMENT '请求时间',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_time` (`company_id`, `request_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾Token消耗';
+
+-- ----------------------------
+-- Table structure for lobster_tool_exec_log
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_tool_exec_log`;
+CREATE TABLE `lobster_tool_exec_log` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `tool_name` varchar(100) DEFAULT NULL COMMENT '工具名',
+    `params_json` text COMMENT '参数JSON',
+    `result_json` text COMMENT '结果JSON',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_id` (`company_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾工具执行日志';
+
+-- ----------------------------
+-- Table structure for lobster_user_preference
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_user_preference`;
+CREATE TABLE `lobster_user_preference` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `external_user_id` varchar(128) NOT NULL COMMENT '外部用户ID',
+    `snapshot_json` text COMMENT '偏好快照JSON',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_company_user` (`company_id`, `external_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾用户偏好';
+
+-- ----------------------------
+-- Table structure for lobster_user_segment
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_user_segment`;
+CREATE TABLE `lobster_user_segment` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `segment_code` varchar(64) NOT NULL COMMENT '分群编码',
+    `segment_name` varchar(128) DEFAULT NULL COMMENT '分群名称',
+    `description` varchar(500) DEFAULT NULL COMMENT '描述',
+    `strategy_config` text COMMENT '策略配置JSON',
+    `deleted` tinyint DEFAULT 0 COMMENT '删除标记',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_company_segment` (`company_id`, `segment_code`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾用户分群';
+
+-- ----------------------------
+-- Table structure for lobster_vector_embeddings
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_vector_embeddings`;
+CREATE TABLE `lobster_vector_embeddings` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `doc_id` varchar(128) DEFAULT NULL COMMENT '文档ID',
+    `doc_type` varchar(50) DEFAULT NULL COMMENT '文档类型',
+    `content` text COMMENT '内容',
+    `embedding` blob COMMENT '向量',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_doc` (`company_id`, `doc_type`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾向量嵌入';
+
+-- ----------------------------
+-- Table structure for lobster_workflow_patch
+-- ----------------------------
+DROP TABLE IF EXISTS `lobster_workflow_patch`;
+CREATE TABLE `lobster_workflow_patch` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint NOT NULL COMMENT '公司ID',
+    `target_table` varchar(100) DEFAULT NULL COMMENT '目标表',
+    `target_id` bigint DEFAULT NULL COMMENT '目标ID',
+    `field_name` varchar(100) DEFAULT NULL COMMENT '字段名',
+    `old_value` text COMMENT '旧值',
+    `new_value` text COMMENT '新值',
+    `reason` varchar(500) DEFAULT NULL COMMENT '原因',
+    `status` varchar(30) DEFAULT 'pending' COMMENT '状态',
+    `apply_time` datetime DEFAULT NULL COMMENT '应用时间',
+    `reject_time` datetime DEFAULT NULL COMMENT '拒绝时间',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_company_status` (`company_id`, `status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='龙虾工作流补丁';
+
+-- ----------------------------
+-- Table structure for user_daily_stats
+-- ----------------------------
+DROP TABLE IF EXISTS `user_daily_stats`;
+CREATE TABLE `user_daily_stats` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `company_id` bigint DEFAULT NULL COMMENT '公司ID',
+    `company_name` varchar(200) DEFAULT NULL COMMENT '公司名称',
+    `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
+    `dept_name` varchar(200) DEFAULT NULL COMMENT '部门名称',
+    `user_id` bigint DEFAULT NULL COMMENT '用户ID',
+    `user_name` varchar(200) DEFAULT NULL COMMENT '用户名',
+    `nick_name` varchar(200) DEFAULT NULL COMMENT '昵称',
+    `statistics_time` varchar(20) DEFAULT NULL COMMENT '统计日期',
+    `line_num` int DEFAULT 0 COMMENT '进线数',
+    `active_num` int DEFAULT 0 COMMENT '活跃数',
+    `complete_num` int DEFAULT 0 COMMENT '完课数',
+    `answer_num` int DEFAULT 0 COMMENT '答题数',
+    `red_packet_num` int DEFAULT 0 COMMENT '红包数',
+    `red_packet_amount` decimal(10,2) DEFAULT NULL COMMENT '红包金额',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_user_date` (`user_id`, `statistics_time`),
+    KEY `idx_dept_date` (`dept_id`, `statistics_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户日统计';
+
+-- ----------------------------
+-- Table structure for watch_alarm_data
+-- ----------------------------
+DROP TABLE IF EXISTS `watch_alarm_data`;
+CREATE TABLE `watch_alarm_data` (
+    `id` char(36) NOT NULL COMMENT '主键UUID',
+    `device_id` varchar(64) DEFAULT NULL COMMENT '设备编号',
+    `title` varchar(200) DEFAULT NULL COMMENT '预警标题',
+    `description` text COMMENT '预警详情',
+    `date_time` varchar(50) DEFAULT NULL COMMENT '预警时间',
+    `type` varchar(50) DEFAULT NULL COMMENT '预警类型',
+    `extra` varchar(500) DEFAULT NULL COMMENT '预警事件',
+    `location` varchar(200) DEFAULT NULL COMMENT '经纬度',
+    `status` tinyint DEFAULT 0 COMMENT 'web已读 0未读1已读',
+    `app_status` tinyint DEFAULT 0 COMMENT 'app已读 0未读1已读',
+    `is_del` tinyint DEFAULT 0 COMMENT '是否删除',
+    `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
+    `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    PRIMARY KEY (`id`),
+    KEY `idx_device_id` (`device_id`),
+    KEY `idx_app_status` (`app_status`, `is_del`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备预警数据';
+
+-- ----------------------------
+-- Table structure for wx_sop_logs
+-- ----------------------------
+DROP TABLE IF EXISTS `wx_sop_logs`;
+CREATE TABLE `wx_sop_logs` (
+    `id` bigint NOT NULL AUTO_INCREMENT,
+    `type` tinyint DEFAULT NULL COMMENT '类型',
+    `sop_id` bigint DEFAULT NULL COMMENT 'SOP ID',
+    `sop_user_id` bigint DEFAULT NULL COMMENT 'SOP用户ID',
+    `send_type` tinyint DEFAULT NULL COMMENT '发送类型',
+    `generate_type` tinyint DEFAULT NULL COMMENT '生成类型',
+    `account_id` bigint DEFAULT NULL COMMENT '账号ID',
+    `wx_contact_id` bigint DEFAULT NULL COMMENT '微信联系人ID',
+    `wx_contact_name` varchar(200) DEFAULT NULL COMMENT '联系人名称',
+    `wx_room_id` bigint DEFAULT NULL COMMENT '群ID',
+    `wx_room_name` varchar(200) DEFAULT NULL COMMENT '群名称',
+    `fs_user_id` bigint DEFAULT NULL COMMENT '销售ID',
+    `send_status` tinyint DEFAULT NULL COMMENT '发送状态',
+    `send_remark` varchar(500) DEFAULT NULL COMMENT '发送备注',
+    `send_sort` int DEFAULT NULL COMMENT '发送排序',
+    `content_json` text COMMENT '内容JSON',
+    `send_time` datetime DEFAULT NULL COMMENT '发送时间',
+    `expiration_time` datetime DEFAULT NULL COMMENT '过期时间',
+    `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+    `create_by` varchar(64) DEFAULT NULL COMMENT '创建人',
+    `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+    `update_by` varchar(64) DEFAULT NULL COMMENT '更新人',
+    `remark` varchar(500) DEFAULT NULL COMMENT '备注',
+    PRIMARY KEY (`id`),
+    KEY `idx_sop_id` (`sop_id`),
+    KEY `idx_sop_user_id` (`sop_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='微信SOP发送日志';

+ 5 - 0
fs-service/src/main/resources/mapper/company/LobsterWorkflowInstanceMapper.xml

@@ -112,4 +112,9 @@
         SELECT workflow_id FROM lobster_workflow_instance WHERE id = #{id}
     </select>
 
+    <select id="countByStatus" resultType="java.lang.Integer">
+        SELECT COUNT(*) FROM lobster_workflow_instance WHERE del_flag = 0 AND status = #{status}
+        <if test="companyId != null">AND company_id = #{companyId}</if>
+    </select>
+
 </mapper>

+ 10 - 0
fs-service/src/main/resources/mapper/lobster/LobsterAuxiliaryMapper.xml

@@ -142,6 +142,16 @@
         SELECT 1 FROM lobster_dead_letter_queue LIMIT 1
     </select>
 
+    <select id="countDeadLetterPending" resultType="java.lang.Integer">
+        SELECT COUNT(*) FROM lobster_dead_letter_queue WHERE status = 'pending'
+        <if test="companyId != null">AND company_id = #{companyId}</if>
+    </select>
+
+    <select id="sumTodayTokens" resultType="java.lang.Long">
+        SELECT COALESCE(SUM(token_count), 0) FROM lobster_token_consume_log WHERE DATE(create_time) = CURDATE()
+        <if test="companyId != null">AND company_id = #{companyId}</if>
+    </select>
+
     <!-- === lobster_e2e_test === -->
     <insert id="insertE2eTest" useGeneratedKeys="true" keyProperty="id">
         INSERT INTO lobster_e2e_test(company_id, test_name, workflow_id, test_data, create_time)

+ 10 - 0
fs-service/src/main/resources/mapper/tenant/TenantInfoMapper.xml

@@ -761,4 +761,14 @@
         UPDATE sys_user SET password = #{password}, update_time = NOW()
         WHERE user_id = 1
     </update>
+
+    <!-- 检查租户库 sys_menu 中是否存在指定菜单(须在租户数据源下调用) -->
+    <select id="countTenantSysMenuById" resultType="int">
+        SELECT COUNT(1) FROM `sys_menu` WHERE menu_id = #{menuId}
+    </select>
+
+    <!-- 检查租户库 company_menu 中是否存在指定菜单(须在租户数据源下调用) -->
+    <select id="countTenantComMenuById" resultType="int">
+        SELECT COUNT(1) FROM `company_menu` WHERE menu_id = #{menuId}
+    </select>
 </mapper>

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

@@ -46,7 +46,7 @@ public class UserSignScrmController extends AppBaseController {
     public R getUserSign(HttpServletRequest request){
         FsUserScrm user=userService.selectFsUserById(Long.parseLong(getUserId()));
         //获取签到配置
-        String json=configService.selectConfigByKey("store.sign");
+        String json=configService.selectConfigByKey("his.sign");
         //判断用户昨天是否签到过
         Long signNum=signService.getSign(user);
         Boolean isDaySign=signService.isDaySign(user);