Explorar o código

总后台龙虾引擎菜单报错处理

lk hai 10 horas
pai
achega
05117d9e79
Modificáronse 17 ficheiros con 721 adicións e 199 borrados
  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. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyMapper.java
  6. 6 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterAuxiliaryMapper.java
  7. 42 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java
  8. 3 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterWorkflowInstanceMapper.java
  9. 27 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterEvolutionSuggestionService.java
  10. 11 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterInstanceStatsService.java
  11. 22 0
      fs-service/src/main/java/com/fs/company/service/workflow/IWorkflowTemplateAdminService.java
  12. 86 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterEvolutionSuggestionServiceImpl.java
  13. 48 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/LobsterInstanceStatsServiceImpl.java
  14. 162 0
      fs-service/src/main/java/com/fs/company/service/workflow/impl/WorkflowTemplateAdminServiceImpl.java
  15. 1 0
      fs-service/src/main/resources/db/tenant-initTable-migration.sql
  16. 5 0
      fs-service/src/main/resources/mapper/company/LobsterWorkflowInstanceMapper.xml
  17. 10 0
      fs-service/src/main/resources/mapper/lobster/LobsterAuxiliaryMapper.xml

+ 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);
+//    }
+
+}

+ 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; }
+    }
+}

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

@@ -984,6 +984,7 @@ 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,
   PRIMARY KEY (`id`),
   KEY `idx_scenario` (`scenario`),
   KEY `idx_company` (`company_id`)

+ 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)