Procházet zdrojové kódy

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

xgb před 4 dny
rodič
revize
715aee1e24
100 změnil soubory, kde provedl 2538 přidání a 1691 odebrání
  1. 9 0
      fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyWorkflowTagTemplateBindingController.java
  2. 11 36
      fs-company/src/main/java/com/fs/company/controller/workflow/LobsterBillingController.java
  3. 18 32
      fs-company/src/main/java/com/fs/company/controller/workflow/LobsterEventAuditController.java
  4. 14 73
      fs-company/src/main/java/com/fs/company/controller/workflow/LobsterPromptController.java
  5. 12 31
      fs-company/src/main/java/com/fs/company/controller/workflow/LobsterSalesCorpusController.java
  6. 33 0
      fs-service/src/main/java/com/fs/company/domain/LobsterAbTest.java
  7. 30 0
      fs-service/src/main/java/com/fs/company/domain/LobsterApiRegistry.java
  8. 32 0
      fs-service/src/main/java/com/fs/company/domain/LobsterChannelTypeRegistry.java
  9. 12 25
      fs-service/src/main/java/com/fs/company/domain/LobsterConversationSummary.java
  10. 23 0
      fs-service/src/main/java/com/fs/company/domain/LobsterDialogueState.java
  11. 29 0
      fs-service/src/main/java/com/fs/company/domain/LobsterEventAudit.java
  12. 20 0
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionConfig.java
  13. 20 0
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionHistory.java
  14. 25 0
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionLog.java
  15. 24 0
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionSuggestion.java
  16. 20 0
      fs-service/src/main/java/com/fs/company/domain/LobsterFeedbackRecord.java
  17. 19 0
      fs-service/src/main/java/com/fs/company/domain/LobsterHandoffEvent.java
  18. 24 0
      fs-service/src/main/java/com/fs/company/domain/LobsterHandoffRule.java
  19. 19 0
      fs-service/src/main/java/com/fs/company/domain/LobsterIdentityHidingConfig.java
  20. 32 0
      fs-service/src/main/java/com/fs/company/domain/LobsterIndustryPattern.java
  21. 29 0
      fs-service/src/main/java/com/fs/company/domain/LobsterIndustryPractice.java
  22. 23 0
      fs-service/src/main/java/com/fs/company/domain/LobsterIntentNodeMapping.java
  23. 31 0
      fs-service/src/main/java/com/fs/company/domain/LobsterKnowledgeVersion.java
  24. 29 0
      fs-service/src/main/java/com/fs/company/domain/LobsterLearningCorpus.java
  25. 41 0
      fs-service/src/main/java/com/fs/company/domain/LobsterLearningEvent.java
  26. 31 0
      fs-service/src/main/java/com/fs/company/domain/LobsterLearningPattern.java
  27. 33 0
      fs-service/src/main/java/com/fs/company/domain/LobsterLearningResult.java
  28. 23 0
      fs-service/src/main/java/com/fs/company/domain/LobsterMessageDeliveryLog.java
  29. 28 0
      fs-service/src/main/java/com/fs/company/domain/LobsterMessageVariant.java
  30. 25 0
      fs-service/src/main/java/com/fs/company/domain/LobsterNodeOptimizationConfig.java
  31. 26 0
      fs-service/src/main/java/com/fs/company/domain/LobsterPendingKnowledge.java
  32. 34 0
      fs-service/src/main/java/com/fs/company/domain/LobsterPromptConfig.java
  33. 25 0
      fs-service/src/main/java/com/fs/company/domain/LobsterSegmentMessageOverride.java
  34. 24 0
      fs-service/src/main/java/com/fs/company/domain/LobsterSegmentRule.java
  35. 23 0
      fs-service/src/main/java/com/fs/company/domain/LobsterSensitiveWord.java
  36. 19 0
      fs-service/src/main/java/com/fs/company/domain/LobsterTenantKeyword.java
  37. 26 0
      fs-service/src/main/java/com/fs/company/domain/LobsterToolConfig.java
  38. 21 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserEvent.java
  39. 32 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserNodeInteraction.java
  40. 52 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserNodeOptimization.java
  41. 20 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserPreference.java
  42. 26 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserSegment.java
  43. 20 0
      fs-service/src/main/java/com/fs/company/domain/LobsterUserSegmentRel.java
  44. 32 0
      fs-service/src/main/java/com/fs/company/domain/LobsterVectorStore.java
  45. 4 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyLobsterTagUserRelMapper.java
  46. 23 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterAbTestMapper.java
  47. 13 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterApiRegistryMapper.java
  48. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterChannelTypeRegistryMapper.java
  49. 12 4
      fs-service/src/main/java/com/fs/company/mapper/LobsterComplianceRuleMapper.java
  50. 31 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterDialogueStateMapper.java
  51. 23 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEventAuditMapper.java
  52. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionConfigMapper.java
  53. 15 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionHistoryMapper.java
  54. 31 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionLogMapper.java
  55. 29 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java
  56. 26 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterFeedbackRecordMapper.java
  57. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterHandoffEventMapper.java
  58. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterHandoffRuleMapper.java
  59. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterIdentityHidingConfigMapper.java
  60. 26 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterIndustryPatternMapper.java
  61. 31 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterIndustryPracticeMapper.java
  62. 24 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterIntentNodeMappingMapper.java
  63. 21 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterKnowledgeVersionMapper.java
  64. 28 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterLearningCorpusMapper.java
  65. 77 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterLearningEventMapper.java
  66. 18 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterLearningPatternMapper.java
  67. 26 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterLearningResultMapper.java
  68. 9 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterMessageVariantMapper.java
  69. 27 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterNodeOptimizationConfigMapper.java
  70. 32 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterPendingKnowledgeMapper.java
  71. 35 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterPromptConfigMapper.java
  72. 15 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterSegmentMessageOverrideMapper.java
  73. 21 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterSegmentRuleMapper.java
  74. 16 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterSensitiveWordMapper.java
  75. 5 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterSystemPromptMapper.java
  76. 14 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterTenantKeywordMapper.java
  77. 25 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterToolConfigMapper.java
  78. 15 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserEventMapper.java
  79. 46 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserNodeInteractionMapper.java
  80. 53 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserNodeOptimizationMapper.java
  81. 22 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserPreferenceMapper.java
  82. 17 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserSegmentMapper.java
  83. 25 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterUserSegmentRelMapper.java
  84. 30 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterVectorStoreMapper.java
  85. 2 0
      fs-service/src/main/java/com/fs/company/service/ICompanyTagTemplateBindingService.java
  86. 32 1
      fs-service/src/main/java/com/fs/company/service/billing/BillingService.java
  87. 6 0
      fs-service/src/main/java/com/fs/company/service/impl/CompanyTagTemplateBindingServiceImpl.java
  88. 20 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterEventAuditService.java
  89. 10 0
      fs-service/src/main/java/com/fs/company/service/workflow/ILobsterLearningCorpusService.java
  90. 6 0
      fs-service/src/main/java/com/fs/company/service/workflow/PendingAuditKnowledgeService.java
  91. 46 60
      fs-service/src/main/java/com/fs/company/service/workflow/api/ApiRegistryService.java
  92. 10 78
      fs-service/src/main/java/com/fs/company/service/workflow/channel/ChannelTypeRegistry.java
  93. 13 76
      fs-service/src/main/java/com/fs/company/service/workflow/event/UserEventMonitor.java
  94. 40 146
      fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/EvolutionEngineImpl.java
  95. 48 157
      fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/EvolutionSchedulerImpl.java
  96. 149 566
      fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/UserNodeOptimizerImpl.java
  97. 56 167
      fs-service/src/main/java/com/fs/company/service/workflow/feedback/impl/FeedbackDrivenEvolutionImpl.java
  98. 62 87
      fs-service/src/main/java/com/fs/company/service/workflow/handoff/impl/HumanHandoffDetectorImpl.java
  99. 29 119
      fs-service/src/main/java/com/fs/company/service/workflow/identity/impl/IdentityHidingServiceImpl.java
  100. 0 33
      fs-service/src/main/java/com/fs/company/service/workflow/impl/ComplianceServiceImpl.java

+ 9 - 0
fs-company/src/main/java/com/fs/company/controller/companyWorkflow/CompanyWorkflowTagTemplateBindingController.java

@@ -125,4 +125,13 @@ public class CompanyWorkflowTagTemplateBindingController extends BaseController
                 loginUser.getCompany().getCompanyId(), loginUser.getUsername(),
                 param.getQwCorpId(), param.getUserIds(), param.getTagCodes(),loginUser.getUser().getUserId());
     }
+
+    /**
+     * 企微客户获取龙虾标签
+     */
+    @PostMapping("/lobsterTags")
+    public AjaxResult lobsterTags(@RequestBody List<Long> userIds) {
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        return tagTemplateBindingService.lobsterTags(userIds,loginUser.getUser().getUserId(),loginUser.getCompany().getCompanyId());
+    }
 }

+ 11 - 36
fs-company/src/main/java/com/fs/company/controller/workflow/LobsterBillingController.java

@@ -7,7 +7,6 @@ import com.fs.company.service.billing.BillingService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -26,9 +25,6 @@ public class LobsterBillingController extends BaseController {
     @Autowired(required = false)
     private BillingService billingService;
 
-    @Autowired(required = false)
-    private JdbcTemplate jdbcTemplate;
-
     @Autowired
     private TokenService tokenService;
 
@@ -72,21 +68,10 @@ public class LobsterBillingController extends BaseController {
 
         if (billingService != null) {
             boolean ok = billingService.updateTokenCoefficient(tenantId, coefficient);
-            if (ok) {
-                try {
-                    jdbcTemplate.update("UPDATE tenant_balance SET token_coefficient=? WHERE tenant_id=?", coefficient, tenantId);
-                } catch (Exception ignored) {}
-                return AjaxResult.success("Token系数已更新为 " + coefficient);
-            }
-            return AjaxResult.error("更新失败");
+            return ok ? AjaxResult.success("Token系数已更新为 " + coefficient) : AjaxResult.error("更新失败");
         }
 
-        try {
-            jdbcTemplate.update("UPDATE tenant_balance SET token_coefficient=? WHERE tenant_id=?", coefficient, tenantId);
-            return AjaxResult.success("Token系数已更新为 " + coefficient);
-        } catch (Exception e) {
-            return AjaxResult.error("更新失败: " + e.getMessage());
-        }
+        return AjaxResult.error("计费服务未初始化");
     }
 
     /** 消费记录列表 */
@@ -96,27 +81,17 @@ public class LobsterBillingController extends BaseController {
                               @RequestParam(defaultValue = "10") int size) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long tenantId = loginUser.getCompany().getCompanyId();
-        List<Map<String, Object>> list;
-        try {
-            list = jdbcTemplate.queryForList(
-                    "SELECT * FROM tenant_consume_record WHERE tenant_id=? ORDER BY consume_time DESC LIMIT ? OFFSET ?",
-                    tenantId, size, (page - 1) * size);
-        } catch (Exception e) {
-            list = jdbcTemplate.queryForList(
-                    "SELECT id, tenant_id, consume_type, amount, remark, status, consume_time FROM tenant_consume_record WHERE tenant_id=? ORDER BY consume_time DESC LIMIT ? OFFSET ?",
-                    tenantId, size, (page - 1) * size);
-        }
-
-        Long total;
-        try {
-            total = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM tenant_consume_record WHERE tenant_id=?", Long.class, tenantId);
-        } catch (Exception e) {
-            total = 0L;
-        }
 
         Map<String, Object> result = new LinkedHashMap<>();
-        result.put("list", list);
-        result.put("total", total);
+        if (billingService != null) {
+            List<Map<String, Object>> list = billingService.getConsumeRecords(tenantId, page, size);
+            long total = billingService.countConsumeRecords(tenantId);
+            result.put("list", list);
+            result.put("total", total);
+        } else {
+            result.put("list", Collections.emptyList());
+            result.put("total", 0);
+        }
         result.put("page", page);
         result.put("size", size);
         return AjaxResult.success(result);

+ 18 - 32
fs-company/src/main/java/com/fs/company/controller/workflow/LobsterEventAuditController.java

@@ -4,11 +4,12 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.LobsterEventAudit;
+import com.fs.company.service.workflow.ILobsterEventAuditService;
 import com.fs.company.service.workflow.event.WorkflowPatcher;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -27,7 +28,7 @@ import java.util.*;
 public class LobsterEventAuditController extends BaseController {
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private ILobsterEventAuditService eventAuditService;
 
     @Autowired
     private TokenService tokenService;
@@ -35,8 +36,6 @@ public class LobsterEventAuditController extends BaseController {
     @Autowired(required = false)
     private WorkflowPatcher workflowPatcher;
 
-    private static final String TABLE = "lobster_event_node_audit";
-
     /**
      * 待审列表
      */
@@ -48,21 +47,13 @@ public class LobsterEventAuditController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
 
-        List<Map<String, Object>> list = jdbcTemplate.queryForList(
-                "SELECT * FROM " + TABLE + " WHERE company_id=? AND status=? ORDER BY create_time DESC LIMIT ? OFFSET ?",
-                companyId, status, size, (page - 1) * size);
-
-        Long total = jdbcTemplate.queryForObject(
-                "SELECT COUNT(*) FROM " + TABLE + " WHERE company_id=? AND status=?", Long.class, companyId, status);
+        List<LobsterEventAudit> list = eventAuditService.selectListByCompanyIdAndStatus(companyId, status, page, size);
+        long total = eventAuditService.countByCompanyIdAndStatus(companyId, status);
 
-        // 统计各状态数量
         Map<String, Object> stats = new LinkedHashMap<>();
-        stats.put("pending", jdbcTemplate.queryForObject(
-                "SELECT COUNT(*) FROM " + TABLE + " WHERE company_id=? AND status='pending'", Long.class, companyId));
-        stats.put("approved", jdbcTemplate.queryForObject(
-                "SELECT COUNT(*) FROM " + TABLE + " WHERE company_id=? AND status='approved'", Long.class, companyId));
-        stats.put("rejected", jdbcTemplate.queryForObject(
-                "SELECT COUNT(*) FROM " + TABLE + " WHERE company_id=? AND status='rejected'", Long.class, companyId));
+        stats.put("pending", eventAuditService.countByCompanyIdAndStatus(companyId, "pending"));
+        stats.put("approved", eventAuditService.countByCompanyIdAndStatus(companyId, "approved"));
+        stats.put("rejected", eventAuditService.countByCompanyIdAndStatus(companyId, "rejected"));
 
         Map<String, Object> result = new LinkedHashMap<>();
         result.put("list", list);
@@ -81,15 +72,14 @@ public class LobsterEventAuditController extends BaseController {
     public AjaxResult approve(@PathVariable Long id) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
-        Map<String, Object> record = jdbcTemplate.queryForMap(
-                "SELECT * FROM " + TABLE + " WHERE id=? AND status='pending'", id);
-        if (record == null || record.isEmpty()) {
+        LobsterEventAudit record = eventAuditService.selectByIdAndStatus(id, "pending");
+        if (record == null) {
             return AjaxResult.error("审核记录不存在或已处理");
         }
 
-        Long instanceId = record.get("instance_id") != null ? ((Number) record.get("instance_id")).longValue() : null;
-        String nodeJson = (String) record.get("node_json");
-        String insertAt = (String) record.get("insert_at");
+        Long instanceId = record.getInstanceId();
+        String nodeJson = record.getNodeJson();
+        String insertAt = record.getInsertAt();
 
         boolean patched = false;
         if (instanceId != null && nodeJson != null && workflowPatcher != null) {
@@ -101,9 +91,8 @@ public class LobsterEventAuditController extends BaseController {
             }
         }
 
-        jdbcTemplate.update(
-                "UPDATE " + TABLE + " SET status='approved', audit_by=?, audit_comment=?, audit_time=NOW(), update_time=NOW() WHERE id=?",
-                loginUser.getUsername(), patched ? "审核通过,节点已注入" : "审核通过", id);
+        String comment = patched ? "审核通过,节点已注入" : "审核通过";
+        eventAuditService.approve(id, loginUser.getUsername(), comment);
 
         Map<String, Object> result = new HashMap<>();
         result.put("patched", patched);
@@ -120,12 +109,9 @@ public class LobsterEventAuditController extends BaseController {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
 
         String comment = body != null ? (String) body.getOrDefault("comment", "人工驳回") : "人工驳回";
+        eventAuditService.reject(id, loginUser.getUsername(), comment);
 
-        int rows = jdbcTemplate.update(
-                "UPDATE " + TABLE + " SET status='rejected', audit_by=?, audit_comment=?, audit_time=NOW(), update_time=NOW() WHERE id=?",
-                loginUser.getUsername(), comment, id);
-
-        return rows > 0 ? AjaxResult.success("已驳回") : AjaxResult.error("审核记录不存在");
+        return AjaxResult.success("已驳回");
     }
 
     /**
@@ -134,7 +120,7 @@ public class LobsterEventAuditController extends BaseController {
     @PreAuthorize("@ss.hasPermi('workflow:lobster:query')")
     @GetMapping("/{id}")
     public AjaxResult detail(@PathVariable Long id) {
-        Map<String, Object> record = jdbcTemplate.queryForMap("SELECT * FROM " + TABLE + " WHERE id=?", id);
+        LobsterEventAudit record = eventAuditService.selectById(id);
         return AjaxResult.success(record);
     }
 }

+ 14 - 73
fs-company/src/main/java/com/fs/company/controller/workflow/LobsterPromptController.java

@@ -3,15 +3,15 @@ package com.fs.company.controller.workflow;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
-import com.fs.company.service.workflow.prompt.SystemPromptService;
+import com.fs.company.domain.LobsterSystemPrompt;
+import com.fs.company.service.workflow.ILobsterPromptService;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.*;
+import java.util.Map;
 
 /**
  * 龙虾系统提示词管理Controller
@@ -23,119 +23,60 @@ import java.util.*;
 public class LobsterPromptController extends BaseController {
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private ILobsterPromptService promptService;
 
     @Autowired
     private TokenService tokenService;
 
-    @Autowired(required = false)
-    private SystemPromptService promptService;
-
-    private static final String TABLE = "lobster_system_prompt";
-
     @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) {
-        StringBuilder where = new StringBuilder(" WHERE enabled=1 ");
-        List<Object> params = new ArrayList<>();
-        if (category != null && !category.isEmpty()) {
-            where.append("AND prompt_category=? ");
-            params.add(category);
-        }
-        if (search != null && !search.isEmpty()) {
-            where.append("AND (prompt_name LIKE ? OR prompt_key LIKE ?) ");
-            params.add("%" + search + "%");
-            params.add("%" + search + "%");
-        }
-        List<Object> countParams = new ArrayList<>(params);
-        // 添加 LIMIT / OFFSET 参数
-        params.add(size);
-        params.add((page - 1) * size);
-        List<Map<String, Object>> list = jdbcTemplate.queryForList(
-                "SELECT * FROM " + TABLE + where + "ORDER BY company_id, industry_type, sort_order LIMIT ? OFFSET ?",
-                params.toArray());
-        Long total = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + TABLE + where, Long.class, countParams.toArray());
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("list", list);
-        result.put("total", total);
-        result.put("page", page);
-        result.put("size", size);
+        Map<String, Object> result = promptService.listPrompts(page, size, category, search);
         return AjaxResult.success(result);
     }
 
     @PreAuthorize("@ss.hasPermi('workflow:lobster:query')")
     @GetMapping("/{id}")
     public AjaxResult getById(@PathVariable Long id) {
-        Map<String, Object> row = jdbcTemplate.queryForMap("SELECT * FROM " + TABLE + " WHERE id=?", id);
-        return AjaxResult.success(row);
+        LobsterSystemPrompt prompt = promptService.getById(id);
+        return AjaxResult.success(prompt);
     }
 
     @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
     @PostMapping
-    public AjaxResult create(@RequestBody Map<String, Object> body) {
+    public AjaxResult create(@RequestBody LobsterSystemPrompt body) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        String key = (String) body.getOrDefault("promptKey", "custom_" + System.currentTimeMillis());
-        String name = (String) body.getOrDefault("promptName", "自定义提示词");
-        String category = (String) body.getOrDefault("promptCategory", "custom");
-        String content = (String) body.get("promptContent");
-        if (content == null || content.isEmpty()) return AjaxResult.error("提示词内容不能为空");
-        jdbcTemplate.update(
-                "INSERT INTO " + TABLE + " (prompt_key,prompt_name,prompt_category,prompt_content,model_name," +
-                "system_role,company_id,industry_type,enabled,sort_order,create_by,create_time) " +
-                "VALUES (?,?,?,?,?,?,?,?,1,0,?,NOW())",
-                key, name, category, content,
-                body.getOrDefault("modelName", "doubao-lite"),
-                body.get("systemRole"),
-                loginUser.getCompany().getCompanyId(),
-                body.get("industryType"),
-                loginUser.getUsername());
-        if (promptService != null) promptService.refreshCache();
+        promptService.create(body, loginUser.getUsername(), loginUser.getCompany().getCompanyId());
         return AjaxResult.success("创建成功");
     }
 
     @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
     @PutMapping("/{id}")
-    public AjaxResult update(@PathVariable Long id, @RequestBody Map<String, Object> body) {
+    public AjaxResult update(@PathVariable Long id, @RequestBody LobsterSystemPrompt body) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        jdbcTemplate.update(
-                "UPDATE " + TABLE + " SET prompt_name=?, prompt_category=?, prompt_content=?, " +
-                "model_name=?, system_role=?, industry_type=?, update_time=NOW() WHERE id=? AND company_id=?",
-                body.getOrDefault("promptName", ""),
-                body.getOrDefault("promptCategory", ""),
-                body.get("promptContent"),
-                body.getOrDefault("modelName", "doubao-lite"),
-                body.get("systemRole"),
-                body.get("industryType"),
-                id, loginUser.getCompany().getCompanyId());
-        if (promptService != null) promptService.refreshCache();
+        promptService.update(id, body, loginUser.getCompany().getCompanyId());
         return AjaxResult.success("更新成功");
     }
 
     @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
     @DeleteMapping("/{id}")
     public AjaxResult delete(@PathVariable Long id) {
-        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        jdbcTemplate.update("UPDATE " + TABLE + " SET enabled=0, update_time=NOW() WHERE id=? AND company_id=?",
-                id, loginUser.getCompany().getCompanyId());
-        if (promptService != null) promptService.refreshCache();
+        promptService.softDelete(id);
         return AjaxResult.success("删除成功");
     }
 
     @GetMapping("/categories")
     public AjaxResult categories() {
-        List<String> cats = jdbcTemplate.queryForList(
-                "SELECT DISTINCT prompt_category FROM " + TABLE + " WHERE enabled=1", String.class);
-        return AjaxResult.success(cats);
+        return AjaxResult.success(promptService.getCategories());
     }
 
     @PreAuthorize("@ss.hasPermi('workflow:lobster:edit')")
     @PostMapping("/refresh-cache")
     public AjaxResult refreshCache() {
-        if (promptService != null) promptService.refreshCache();
+        promptService.refreshCache();
         return AjaxResult.success("缓存已刷新");
     }
 }

+ 12 - 31
fs-company/src/main/java/com/fs/company/controller/workflow/LobsterSalesCorpusController.java

@@ -3,14 +3,16 @@ package com.fs.company.controller.workflow;
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.utils.ServletUtils;
+import com.fs.company.domain.LobsterLearningCorpus;
+import com.fs.company.service.workflow.ILobsterLearningCorpusService;
 import com.fs.company.service.workflow.learning.SalesCorpusAnalyzer;
 import com.fs.company.service.workflow.learning.SalesCorpusAnalyzer.AnalysisReport;
 import com.fs.company.service.workflow.learning.SalesCorpusAnalyzer.CorpusEntry;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.TokenService;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -31,14 +33,12 @@ public class LobsterSalesCorpusController extends BaseController {
     @Autowired(required = false)
     private SalesCorpusAnalyzer corpusAnalyzer;
 
-    @Autowired(required = false)
-    private JdbcTemplate jdbcTemplate;
+    @Autowired
+    private ILobsterLearningCorpusService corpusService;
 
     @Autowired
     private TokenService tokenService;
 
-    private static final String TABLE = "lobster_learning_corpus";
-
     /**
      * 单条录入销冠对话
      */
@@ -150,35 +150,16 @@ public class LobsterSalesCorpusController extends BaseController {
      */
     @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 scenario,
-                           @RequestParam(required = false) String status) {
+    public TableDataInfo list(@RequestParam(defaultValue = "1") int page,
+                              @RequestParam(defaultValue = "10") int size,
+                              @RequestParam(required = false) String scenario,
+                              @RequestParam(required = false) String status) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         Long companyId = loginUser.getCompany().getCompanyId();
 
-        StringBuilder where = new StringBuilder("WHERE company_id=? ");
-        List<Object> params = new ArrayList<>();
-        params.add(companyId);
-        if (scenario != null && !scenario.isEmpty()) {
-            where.append("AND scenario=? ");
-            params.add(scenario);
-        }
-        if (status != null && !status.isEmpty()) {
-            where.append("AND status=? ");
-            params.add(status);
-        }
-
-        List<Map<String, Object>> list = jdbcTemplate != null ?
-                jdbcTemplate.queryForList("SELECT * FROM " + TABLE + " " + where +
-                        "ORDER BY create_time DESC LIMIT ? OFFSET ?",
-                        params.toArray(new Object[0]).clone()) : Collections.emptyList();
-
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("list", list);
-        result.put("page", page);
-        result.put("size", size);
-        return AjaxResult.success(result);
+        startPage();
+        List<LobsterLearningCorpus> list = corpusService.selectListByCompanyId(companyId, scenario, status);
+        return getDataTable(list);
     }
 
     /**

+ 33 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterAbTest.java

@@ -0,0 +1,33 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_ab_tests")
+public class LobsterAbTest {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String nodeCode;
+
+    @TableField("original_message")
+    private String originalMessage;
+
+    @TableField("variant_message")
+    private String variantMessage;
+
+    @TableField("original_positive_rate")
+    private Double originalPositiveRate;
+
+    @TableField("variant_positive_rate")
+    private Double variantPositiveRate;
+
+    private String status;
+    private LocalDateTime appliedAt;
+    private LocalDateTime createTime;
+}

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

@@ -0,0 +1,30 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_api_registry")
+public class LobsterApiRegistry {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String apiKey;
+    private String apiName;
+    private String category;
+    private String provider;
+    private String baseUrl;
+    private String authType;
+    private String authConfig;
+    private Integer timeout;
+    private Integer priority;
+    private Integer isBackup;
+    private String description;
+    private Integer enabled;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterChannelTypeRegistry.java

@@ -0,0 +1,32 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_channel_type_registry")
+public class LobsterChannelTypeRegistry {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @TableField("channel_type")
+    private String channelType;
+
+    @TableField("display_name")
+    private String displayName;
+
+    @TableField("source_table")
+    private String sourceTable;
+
+    @TableField("user_id_column")
+    private String userIdColumn;
+
+    private Integer enabled;
+
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 12 - 25
fs-service/src/main/java/com/fs/company/domain/LobsterConversationSummary.java

@@ -2,36 +2,23 @@ package com.fs.company.domain;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
-import com.fs.common.core.domain.BaseEntity;
+import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
-import lombok.EqualsAndHashCode;
+import java.time.LocalDateTime;
 
 @Data
-@EqualsAndHashCode(callSuper = true)
-public class LobsterConversationSummary extends BaseEntity {
-
-    private static final long serialVersionUID = 1L;
-
+@TableName("lobster_conversation_summary")
+public class LobsterConversationSummary {
     @TableId(type = IdType.AUTO)
     private Long id;
-
     private Long companyId;
-
-    private Long instanceId;
-
-    private Long contactId;
-
-    private String summaryType;
-
-    private String summaryContent;
-
-    private String keyPoints;
-
-    private String sentimentAnalysis;
-
-    private String nextActionSuggestion;
-
+    private String externalUserId;
+    private String sessionId;
+    private String summaryText;
+    private String keywords;
+    private String sentiment;
+    private String intentCategory;
     private Integer messageCount;
-
-    private Integer delFlag;
+    private LocalDateTime summaryTime;
+    private LocalDateTime createTime;
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterDialogueState.java

@@ -0,0 +1,23 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_dialogue_state")
+public class LobsterDialogueState {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long instanceId;
+    private String nodeCode;
+
+    @TableField("state_json")
+    private String stateJson;
+
+    private LocalDateTime updateTime;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterEventAudit.java

@@ -0,0 +1,29 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_event_node_audit")
+public class LobsterEventAudit {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long instanceId;
+    private String nodeType;
+    private String nodeName;
+    private String nodeJson;
+    private String insertAt;
+    private String reason;
+    private String status;
+    private String auditBy;
+    private String auditComment;
+    private LocalDateTime auditTime;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionConfig.java

@@ -0,0 +1,20 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_evolution_config")
+public class LobsterEvolutionConfig {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String configKey;
+    private String configValue;
+    private String description;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionHistory.java

@@ -0,0 +1,20 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_evolution_history")
+public class LobsterEvolutionHistory {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String evolveType;
+    private String description;
+    private String detail;
+    private String status;
+    private LocalDateTime createTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionLog.java

@@ -0,0 +1,25 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_evolution_log")
+public class LobsterEvolutionLog {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private String actionType;
+    private String nodeCode;
+    private String beforeContent;
+    private String afterContent;
+    private String changeDesc;
+    private Double improvementRate;
+    private String status;
+    private LocalDateTime evolveTime;
+    private LocalDateTime createTime;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionSuggestion.java

@@ -0,0 +1,24 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_evolution_suggestion")
+public class LobsterEvolutionSuggestion {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private String nodeCode;
+    private String suggestionType;
+    private String suggestionContent;
+    private String originalContent;
+    private Double confidenceScore;
+    private String status;
+    private String createdBy;
+    private LocalDateTime createTime;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterFeedbackRecord.java

@@ -0,0 +1,20 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_feedback_records")
+public class LobsterFeedbackRecord {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long instanceId;
+    private String nodeCode;
+    private String feedbackType;
+    private String comment;
+    private LocalDateTime createTime;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterHandoffEvent.java

@@ -0,0 +1,19 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_handoff_events")
+public class LobsterHandoffEvent {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long instanceId;
+    private String reason;
+    private String urgency;
+    private LocalDateTime createTime;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterHandoffRule.java

@@ -0,0 +1,24 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_handoff_rules")
+public class LobsterHandoffRule {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String ruleName;
+    private String ruleType;
+    private String conditionExpr;
+    private String urgency;
+    private String handoffMessage;
+    private Integer enabled;
+    private Integer priority;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterIdentityHidingConfig.java

@@ -0,0 +1,19 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_identity_hiding_config")
+public class LobsterIdentityHidingConfig {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String configKey;
+    private String configValue;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterIndustryPattern.java

@@ -0,0 +1,32 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_industry_patterns")
+public class LobsterIndustryPattern {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String industry;
+    private String scenario;
+
+    @TableField("pattern_type")
+    private String patternType;
+
+    @TableField("pattern_content")
+    private String patternContent;
+
+    @TableField("effectiveness_score")
+    private Double effectivenessScore;
+
+    @TableField("contributor_count")
+    private Integer contributorCount;
+
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterIndustryPractice.java

@@ -0,0 +1,29 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_industry_practices")
+public class LobsterIndustryPractice {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String industry;
+    private String scenario;
+    private String content;
+
+    @TableField("effectiveness_score")
+    private Double effectivenessScore;
+
+    @TableField("contributor_count")
+    private Integer contributorCount;
+
+    @TableField("pattern_type")
+    private String patternType;
+
+    private LocalDateTime createTime;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterIntentNodeMapping.java

@@ -0,0 +1,23 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_intent_node_mapping")
+public class LobsterIntentNodeMapping {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String intent;
+    private String sentiment;
+    private String currentNodeCode;
+    private String targetNodeCode;
+    private String conditionExpr;
+    private Integer priority;
+    private Integer enabled;
+    private LocalDateTime createTime;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterKnowledgeVersion.java

@@ -0,0 +1,31 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_knowledge_versions")
+public class LobsterKnowledgeVersion {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long knowledgeId;
+    private Integer version;
+    private String title;
+
+    private String contentSnapshot;
+
+    private String category;
+    private String tag;
+
+    private String changeType;
+
+    private String changeReason;
+
+    private String changedBy;
+
+    private LocalDateTime createTime;
+}

+ 29 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterLearningCorpus.java

@@ -0,0 +1,29 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_learning_corpus")
+public class LobsterLearningCorpus {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String salespersonName;
+    private String customerQuestion;
+    private String salesAnswer;
+    private String scenario;
+    private String industryType;
+    private String tags;
+    private String status;
+    private String aiAnalysis;
+    private Integer usageCount;
+    private LocalDateTime analyzeTime;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 41 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterLearningEvent.java

@@ -0,0 +1,41 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_learning_events")
+public class LobsterLearningEvent {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private Long instanceId;
+    private Long contactId;
+    private String channelType;
+    private String nodeCode;
+    private String nodeType;
+    private String sentMessage;
+
+    @TableField("customer_reply_content")
+    private String customerReplyContent;
+
+    @TableField("customer_reply_delay_ms")
+    private Long customerReplyDelayMs;
+
+    @TableField("customer_reply_sentiment")
+    private Double customerReplySentiment;
+
+    private String outcome;
+    private Long durationMs;
+    private Integer sendHour;
+    private Integer sendDayOfWeek;
+    private String variables;
+    private String customerProfile;
+    private Integer analyzed;
+    private LocalDateTime createTime;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterLearningPattern.java

@@ -0,0 +1,31 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_learning_patterns")
+public class LobsterLearningPattern {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String scenario;
+
+    @TableField("strategy_type")
+    private String strategyType;
+
+    @TableField("strategy_content")
+    private String strategyContent;
+
+    private Double confidence;
+    private String reason;
+
+    @TableField("historical_performance")
+    private Double historicalPerformance;
+
+    private LocalDateTime createTime;
+}

+ 33 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterLearningResult.java

@@ -0,0 +1,33 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_learning_results")
+public class LobsterLearningResult {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+
+    @TableField("learning_type")
+    private String learningType;
+
+    private String title;
+    private String description;
+    private String evidence;
+    private Double confidence;
+    private String scope;
+    private String suggestions;
+    private String status;
+
+    @TableField("discovered_at")
+    private LocalDateTime discoveredAt;
+
+    @TableField("applied_at")
+    private LocalDateTime appliedAt;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterMessageDeliveryLog.java

@@ -0,0 +1,23 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_message_delivery_log")
+public class LobsterMessageDeliveryLog {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private String messageId;
+    private Long instanceId;
+    private String nodeCode;
+    private String channel;
+    private String status;
+    private Integer retryCount;
+    private String errorMsg;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 28 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterMessageVariant.java

@@ -0,0 +1,28 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_message_variants")
+public class LobsterMessageVariant {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String nodeCode;
+
+    /** 变体话术内容 */
+    private String content;
+
+    @TableField("generation_reason")
+    private String generationReason;
+
+    /** pending / applied / rejected */
+    private String status;
+
+    private LocalDateTime createTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterNodeOptimizationConfig.java

@@ -0,0 +1,25 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_node_optimization_config")
+public class LobsterNodeOptimizationConfig {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private String nodeCode;
+    private Boolean enabled;
+
+    @TableField("auto_apply")
+    private Boolean autoApply;
+
+    private String configBy;
+    private LocalDateTime updateTime;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterPendingKnowledge.java

@@ -0,0 +1,26 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_pending_knowledge")
+public class LobsterPendingKnowledge {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String question;
+    private String answer;
+    private String source;
+    private String auditStatus;
+    private String auditComment;
+    private String auditor;
+    private LocalDateTime auditTime;
+    private String createBy;
+    private String updateBy;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

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

@@ -0,0 +1,34 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_prompt_config")
+public class LobsterPromptConfig {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+
+    @TableField("workflow_code")
+    private String workflowCode;
+
+    @TableField("node_code")
+    private String nodeCode;
+
+    @TableField("prompt_type")
+    private String promptType;
+
+    private String content;
+    private String scope;
+
+    /** 0=未删除, 1=已删除 */
+    private Integer deleted;
+
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterSegmentMessageOverride.java

@@ -0,0 +1,25 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_segment_message_override")
+public class LobsterSegmentMessageOverride {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String nodeCode;
+
+    @TableField("message_content")
+    private String messageContent;
+
+    private String segmentCode;
+    private Integer deleted;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 24 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterSegmentRule.java

@@ -0,0 +1,24 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_segment_rule")
+public class LobsterSegmentRule {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String segmentCode;
+
+    @TableField("condition_expr")
+    private String conditionExpr;
+
+    private Integer priority;
+    private Integer enabled;
+    private LocalDateTime createTime;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterSensitiveWord.java

@@ -0,0 +1,23 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_sensitive_word")
+public class LobsterSensitiveWord {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String word;
+    private String replacement;
+    private String category;
+    private Integer enabled;
+    private Integer deleted;
+    private LocalDateTime createTime;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterTenantKeyword.java

@@ -0,0 +1,19 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_tenant_keywords")
+public class LobsterTenantKeyword {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String category;
+    private String keywords;
+    private Integer enabled;
+    private LocalDateTime createTime;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterToolConfig.java

@@ -0,0 +1,26 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_tool_config")
+public class LobsterToolConfig {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String toolName;
+    private String toolType;
+    private String description;
+
+    @TableField("config_json")
+    private String configJson;
+
+    private Integer enabled;
+    private Integer deleted;
+    private LocalDateTime createTime;
+}

+ 21 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserEvent.java

@@ -0,0 +1,21 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_events")
+public class LobsterUserEvent {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String contactId;
+    private String eventType;
+    private String eventData;
+    private String processStatus;
+    private LocalDateTime eventTime;
+    private LocalDateTime createTime;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserNodeInteraction.java

@@ -0,0 +1,32 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_node_interaction")
+public class LobsterUserNodeInteraction {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private Long instanceId;
+    private String externalUserId;
+    private String nodeCode;
+    private String sentMessage;
+    private String customerReply;
+
+    @TableField("reply_delay_ms")
+    private Long replyDelayMs;
+
+    private Double sentiment;
+    private String intent;
+    private String outcome;
+
+    @TableField("create_time")
+    private LocalDateTime createTime;
+}

+ 52 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserNodeOptimization.java

@@ -0,0 +1,52 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_node_optimization")
+public class LobsterUserNodeOptimization {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private Long workflowId;
+    private String externalUserId;
+    private String nodeCode;
+
+    @TableField("original_content")
+    private String originalContent;
+
+    @TableField("optimized_content")
+    private String optimizedContent;
+
+    @TableField("optimization_type")
+    private String optimizationType;
+
+    private Double confidence;
+
+    @TableField("optimization_reason")
+    private String optimizationReason;
+
+    @TableField("optimization_detail")
+    private String optimizationDetail;
+
+    private String status;
+
+    @TableField("auditor_id")
+    private String auditorId;
+
+    @TableField("audit_remark")
+    private String auditRemark;
+
+    @TableField("audit_time")
+    private LocalDateTime auditTime;
+
+    @TableField("apply_time")
+    private LocalDateTime applyTime;
+
+    private LocalDateTime createTime;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserPreference.java

@@ -0,0 +1,20 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_preference")
+public class LobsterUserPreference {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String externalUserId;
+    private String preferenceType;
+    private String preferenceValue;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserSegment.java

@@ -0,0 +1,26 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_segment")
+public class LobsterUserSegment {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String segmentCode;
+    private String segmentName;
+    private String description;
+
+    @TableField("strategy_config")
+    private String strategyConfig;
+
+    private Integer deleted;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterUserSegmentRel.java

@@ -0,0 +1,20 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_user_segment_rel")
+public class LobsterUserSegmentRel {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String externalUserId;
+    private String segmentCode;
+    private Integer status;
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

+ 32 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterVectorStore.java

@@ -0,0 +1,32 @@
+package com.fs.company.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+@TableName("lobster_vector_store")
+public class LobsterVectorStore {
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    private Long companyId;
+    private String category;
+
+    @TableField("vec_key")
+    private String vecKey;
+
+    @TableField("text_content")
+    private String textContent;
+
+    /** embedding向量, JSON float[] 格式 */
+    private String vector;
+
+    /** 元数据, JSON Map格式 */
+    private String metadata;
+
+    private LocalDateTime createTime;
+    private LocalDateTime updateTime;
+}

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

@@ -2,9 +2,11 @@ package com.fs.company.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.CompanyLobsterTagUserRel;
+import org.apache.ibatis.annotations.MapKey;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
+import java.util.Map;
 
 public interface CompanyLobsterTagUserRelMapper extends BaseMapper<CompanyLobsterTagUserRel> {
 
@@ -12,4 +14,6 @@ public interface CompanyLobsterTagUserRelMapper extends BaseMapper<CompanyLobste
 
     void updateBatchRelBybinding(@Param("id") Long id,@Param("flag") Integer unDelFlag);
 
+    @MapKey("id")
+    Map<String,String> selectLobsterTagsByExId(@Param("userIds") List<Long> userIds, @Param("userId") Long userId, @Param("companyId") Long companyId);
 }

+ 23 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterAbTestMapper.java

@@ -0,0 +1,23 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterAbTest;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterAbTestMapper extends BaseMapper<LobsterAbTest> {
+
+    @Select("SELECT * FROM lobster_ab_tests WHERE company_id = #{companyId} AND status = 'active'")
+    List<LobsterAbTest> selectActiveByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_ab_tests WHERE id = #{id} AND company_id = #{companyId} AND status = 'active'")
+    LobsterAbTest selectActiveById(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    @Update("UPDATE lobster_ab_tests SET status = 'applied', applied_at = NOW() WHERE id = #{id}")
+    int markApplied(@Param("id") Long id);
+
+    @Update("UPDATE lobster_ab_tests SET status = 'completed' WHERE id = #{id}")
+    int markCompleted(@Param("id") Long id);
+}

+ 13 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterApiRegistryMapper.java

@@ -0,0 +1,13 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterApiRegistry;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterApiRegistryMapper extends BaseMapper<LobsterApiRegistry> {
+
+    List<LobsterApiRegistry> selectEnabled();
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterChannelTypeRegistryMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterChannelTypeRegistry;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterChannelTypeRegistryMapper extends BaseMapper<LobsterChannelTypeRegistry> {
+}

+ 12 - 4
fs-service/src/main/java/com/fs/company/mapper/LobsterComplianceRuleMapper.java

@@ -1,21 +1,29 @@
 package com.fs.company.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.fs.company.domain.LobsterComplianceRule;
+import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 import java.util.List;
 
-public interface LobsterComplianceRuleMapper {
+@Mapper
+public interface LobsterComplianceRuleMapper extends BaseMapper<LobsterComplianceRule> {
 
+    @Select("SELECT * FROM lobster_compliance_rule WHERE company_id = #{companyId}")
     List<LobsterComplianceRule> selectByCompanyId(@Param("companyId") Long companyId);
 
+    @Select("SELECT * FROM lobster_compliance_rule WHERE company_id = #{companyId} AND enabled = 1")
     List<LobsterComplianceRule> selectEnabledByCompanyId(@Param("companyId") Long companyId);
 
+    @Select("SELECT * FROM lobster_compliance_rule WHERE id = #{id} AND company_id = #{companyId}")
     LobsterComplianceRule selectByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
 
-    int insert(LobsterComplianceRule rule);
-
-    int updateById(LobsterComplianceRule rule);
+    @Select("SELECT pattern FROM lobster_compliance_rule WHERE company_id = #{companyId} AND rule_type = 'sensitive' AND enabled = 1")
+    List<String> selectSensitivePatterns(@Param("companyId") Long companyId);
 
+    @Update("UPDATE lobster_compliance_rule SET deleted = 1 WHERE id = #{id} AND company_id = #{companyId}")
     int logicalDeleteById(@Param("id") Long id, @Param("companyId") Long companyId);
 }

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

@@ -0,0 +1,31 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterDialogueState;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterDialogueStateMapper extends BaseMapper<LobsterDialogueState> {
+
+    @Select("SELECT state_json FROM lobster_dialogue_state WHERE instance_id = #{instanceId} AND node_code = #{nodeCode} AND company_id = #{companyId}")
+    String selectStateJson(@Param("instanceId") Long instanceId, @Param("nodeCode") String nodeCode, @Param("companyId") Long companyId);
+
+    @Select("SELECT state_json FROM lobster_dialogue_state WHERE instance_id = #{instanceId} AND node_code = #{nodeCode}")
+    String selectStateJsonNoCompany(@Param("instanceId") Long instanceId, @Param("nodeCode") String nodeCode);
+
+    @Delete("DELETE FROM lobster_dialogue_state WHERE instance_id = #{instanceId} AND node_code = #{nodeCode} AND company_id = #{companyId}")
+    int deleteByInstanceAndNode(@Param("instanceId") Long instanceId, @Param("nodeCode") String nodeCode, @Param("companyId") Long companyId);
+
+    @Delete("DELETE FROM lobster_dialogue_state WHERE instance_id = #{instanceId} AND node_code = #{nodeCode}")
+    int deleteByInstanceAndNodeNoCompany(@Param("instanceId") Long instanceId, @Param("nodeCode") String nodeCode);
+
+    @Insert("INSERT INTO lobster_dialogue_state (company_id, instance_id, node_code, state_json, update_time) " +
+            "VALUES (#{companyId}, #{instanceId}, #{nodeCode}, #{stateJson}, NOW()) " +
+            "ON DUPLICATE KEY UPDATE state_json = #{stateJson}, update_time = NOW()")
+    int upsert(LobsterDialogueState entity);
+
+    @Insert("INSERT INTO lobster_dialogue_state (instance_id, node_code, state_json, update_time) " +
+            "VALUES (#{instanceId}, #{nodeCode}, #{stateJson}, NOW()) " +
+            "ON DUPLICATE KEY UPDATE state_json = #{stateJson}, update_time = NOW()")
+    int upsertNoCompany(LobsterDialogueState entity);
+}

+ 23 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterEventAuditMapper.java

@@ -0,0 +1,23 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterEventAudit;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterEventAuditMapper extends BaseMapper<LobsterEventAudit> {
+
+    List<LobsterEventAudit> selectListByCompanyIdAndStatus(@Param("companyId") Long companyId,
+                                                            @Param("status") String status,
+                                                            @Param("offset") int offset,
+                                                            @Param("limit") int limit);
+
+    long countByCompanyIdAndStatus(@Param("companyId") Long companyId,
+                                   @Param("status") String status);
+
+    LobsterEventAudit selectByIdAndStatus(@Param("id") Long id,
+                                           @Param("status") String status);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionConfigMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterEvolutionConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterEvolutionConfigMapper extends BaseMapper<LobsterEvolutionConfig> {
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionHistoryMapper.java

@@ -0,0 +1,15 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterEvolutionHistory;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Param;
+import java.util.List;
+
+@Mapper
+public interface LobsterEvolutionHistoryMapper extends BaseMapper<LobsterEvolutionHistory> {
+
+    @Select("SELECT * FROM lobster_evolution_history WHERE company_id = #{companyId} ORDER BY evolution_time DESC LIMIT #{limit}")
+    List<LobsterEvolutionHistory> selectByCompanyId(@Param("companyId") Long companyId, @Param("limit") int limit);
+}

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

@@ -0,0 +1,31 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterEvolutionLog;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface LobsterEvolutionLogMapper extends BaseMapper<LobsterEvolutionLog> {
+
+    @Select("SELECT node_code, sent_message,"
+            + " COUNT(*) as total,"
+            + " SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply,"
+            + " ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate"
+            + " FROM lobster_evolution_log"
+            + " WHERE company_id = #{companyId} AND workflow_id = #{workflowId}"
+            + " GROUP BY node_code, sent_message"
+            + " HAVING COUNT(*) >= 3 AND no_reply_rate > 50"
+            + " ORDER BY no_reply_rate DESC LIMIT 5")
+    List<Map<String, Object>> findLowPerformNodes(@Param("companyId") Long companyId,
+                                                   @Param("workflowId") Long workflowId);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_log WHERE company_id = #{companyId}")
+    Integer countByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_log WHERE company_id = #{companyId}"
+            + " AND customer_reply IS NOT NULL AND customer_reply != ''")
+    Integer countRepliedByCompanyId(@Param("companyId") Long companyId);
+}

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

@@ -0,0 +1,29 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterEvolutionSuggestion;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterEvolutionSuggestionMapper extends BaseMapper<LobsterEvolutionSuggestion> {
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId}")
+    Integer countByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'replied'")
+    Integer countRepliedByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'pending'")
+    Integer countPendingByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'applied'")
+    Integer countAppliedByCompanyId(@Param("companyId") Long companyId);
+
+    @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);
+
+    @Update("UPDATE lobster_evolution_suggestion SET status = 'applied' WHERE id = #{id}")
+    int markApplied(@Param("id") Long id);
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterFeedbackRecordMapper.java

@@ -0,0 +1,26 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterFeedbackRecord;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface LobsterFeedbackRecordMapper extends BaseMapper<LobsterFeedbackRecord> {
+
+    @Select("SELECT comment FROM lobster_feedback_records WHERE company_id = #{companyId} AND node_code = #{nodeCode}")
+    List<String> selectCommentsByNodeCode(@Param("companyId") Long companyId, @Param("nodeCode") String nodeCode);
+
+    @Select("SELECT node_code,"
+            + " COUNT(*) as total,"
+            + " SUM(CASE WHEN feedback_type = 'positive' THEN 1 ELSE 0 END) as positive,"
+            + " SUM(CASE WHEN feedback_type = 'negative' THEN 1 ELSE 0 END) as negative,"
+            + " SUM(CASE WHEN feedback_type = 'complaint' THEN 1 ELSE 0 END) as complaint,"
+            + " SUM(CASE WHEN feedback_type = 'conversion' THEN 1 ELSE 0 END) as conversion"
+            + " FROM lobster_feedback_records"
+            + " WHERE company_id = #{companyId}"
+            + " GROUP BY node_code HAVING COUNT(*) >= 5")
+    List<Map<String, Object>> aggregateByNodeCode(@Param("companyId") Long companyId);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterHandoffEventMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterHandoffEvent;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterHandoffEventMapper extends BaseMapper<LobsterHandoffEvent> {
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterHandoffRuleMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterHandoffRule;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterHandoffRuleMapper extends BaseMapper<LobsterHandoffRule> {
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterIdentityHidingConfigMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterIdentityHidingConfig;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterIdentityHidingConfigMapper extends BaseMapper<LobsterIdentityHidingConfig> {
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterIndustryPatternMapper.java

@@ -0,0 +1,26 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterIndustryPattern;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterIndustryPatternMapper extends BaseMapper<LobsterIndustryPattern> {
+
+    @Insert("INSERT INTO lobster_industry_patterns " +
+            "(industry, scenario, pattern_type, pattern_content, effectiveness_score, contributor_count, create_time, update_time) " +
+            "VALUES (#{industry}, #{scenario}, #{patternType}, #{patternContent}, #{effectivenessScore}, 1, NOW(), NOW()) " +
+            "ON DUPLICATE KEY UPDATE " +
+            "effectiveness_score = (effectiveness_score * contributor_count + #{effectivenessScore}) / (contributor_count + 1), " +
+            "contributor_count = contributor_count + 1, update_time = NOW()")
+    int contribute(LobsterIndustryPattern entity);
+
+    @Select("SELECT COUNT(*) FROM lobster_industry_patterns WHERE industry = #{industry}")
+    int countByIndustry(@Param("industry") String industry);
+
+    @Select("SELECT AVG(effectiveness_score) FROM lobster_industry_patterns WHERE industry = #{industry}")
+    Double avgScoreByIndustry(@Param("industry") String industry);
+
+    @Select("SELECT SUM(contributor_count) FROM lobster_industry_patterns WHERE industry = #{industry}")
+    Integer sumContributorsByIndustry(@Param("industry") String industry);
+}

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

@@ -0,0 +1,31 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterIndustryPractice;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterIndustryPracticeMapper extends BaseMapper<LobsterIndustryPractice> {
+
+    @Select("SELECT * FROM lobster_industry_practices WHERE industry = #{industry} " +
+            "ORDER BY effectiveness_score DESC LIMIT #{topK}")
+    List<LobsterIndustryPractice> selectByIndustry(@Param("industry") String industry, @Param("topK") int topK);
+
+    @Select("SELECT * FROM lobster_industry_practices WHERE industry = #{industry} AND scenario = #{scenario} " +
+            "ORDER BY effectiveness_score DESC LIMIT #{topK}")
+    List<LobsterIndustryPractice> selectByIndustryAndScenario(@Param("industry") String industry,
+                                                               @Param("scenario") String scenario,
+                                                               @Param("topK") int topK);
+
+    @Select("SELECT * FROM lobster_industry_practices WHERE id = #{id}")
+    LobsterIndustryPractice selectById(@Param("id") Long id);
+
+    @Select("SELECT COUNT(*) FROM lobster_industry_practices WHERE industry = #{industry}")
+    int countByIndustry(@Param("industry") String industry);
+
+    @Insert("INSERT INTO lobster_industry_application_log (company_id, practice_id, applied_at) " +
+            "VALUES (#{companyId}, #{practiceId}, NOW())")
+    int logApplication(@Param("companyId") Long companyId, @Param("practiceId") Long practiceId);
+}

+ 24 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterIntentNodeMappingMapper.java

@@ -0,0 +1,24 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterIntentNodeMapping;
+import org.apache.ibatis.annotations.*;
+import java.util.List;
+
+@Mapper
+public interface LobsterIntentNodeMappingMapper extends BaseMapper<LobsterIntentNodeMapping> {
+
+    @Select("SELECT target_node_code FROM lobster_intent_node_mapping"
+            + " WHERE company_id = #{companyId} AND intent = #{intent} AND enabled = 1"
+            + " AND (current_node_code = #{currentNodeCode} OR current_node_code = '*' OR current_node_code IS NULL)"
+            + " ORDER BY priority DESC LIMIT 1")
+    String selectTargetNodeByIntent(@Param("companyId") Long companyId,
+                                    @Param("intent") String intent,
+                                    @Param("currentNodeCode") String currentNodeCode);
+
+    @Select("SELECT target_node_code FROM lobster_intent_node_mapping"
+            + " WHERE company_id = #{companyId} AND sentiment = #{sentiment} AND intent = '*' AND enabled = 1"
+            + " ORDER BY priority DESC LIMIT 1")
+    String selectTargetNodeBySentiment(@Param("companyId") Long companyId,
+                                       @Param("sentiment") String sentiment);
+}

+ 21 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterKnowledgeVersionMapper.java

@@ -0,0 +1,21 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterKnowledgeVersion;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterKnowledgeVersionMapper extends BaseMapper<LobsterKnowledgeVersion> {
+
+    @Select("SELECT MAX(version) FROM lobster_knowledge_versions WHERE company_id = #{companyId} AND knowledge_id = #{knowledgeId}")
+    Integer selectMaxVersion(@Param("companyId") Long companyId, @Param("knowledgeId") Long knowledgeId);
+
+    @Select("SELECT * FROM lobster_knowledge_versions WHERE company_id = #{companyId} AND knowledge_id = #{knowledgeId} ORDER BY version DESC")
+    java.util.List<LobsterKnowledgeVersion> selectByKnowledgeId(@Param("companyId") Long companyId, @Param("knowledgeId") Long knowledgeId);
+
+    @Select("SELECT content_snapshot FROM lobster_knowledge_versions WHERE company_id = #{companyId} AND knowledge_id = #{knowledgeId} AND version = #{version} LIMIT 1")
+    String selectContentByVersion(@Param("companyId") Long companyId, @Param("knowledgeId") Long knowledgeId, @Param("version") int version);
+
+    @Select("SELECT title FROM lobster_knowledge_versions WHERE company_id = #{companyId} AND knowledge_id = #{knowledgeId} AND version = #{version} LIMIT 1")
+    String selectTitleByVersion(@Param("companyId") Long companyId, @Param("knowledgeId") Long knowledgeId, @Param("version") int version);
+}

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

@@ -0,0 +1,28 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterLearningCorpus;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterLearningCorpusMapper extends BaseMapper<LobsterLearningCorpus> {
+
+    List<LobsterLearningCorpus> selectListByCompanyId(@Param("companyId") Long companyId,
+                                                       @Param("scenario") String scenario,
+                                                       @Param("status") String status);
+
+    List<LobsterLearningCorpus> selectRawByCompanyId(@Param("companyId") Long companyId,
+                                                      @Param("limit") int limit);
+
+    List<LobsterLearningCorpus> selectByScenario(@Param("companyId") Long companyId,
+                                                  @Param("scenario") String scenario,
+                                                  @Param("limit") int limit);
+
+    int markAnalyzed(@Param("companyId") Long companyId,
+                     @Param("limit") int limit);
+
+    int incrementUsage(@Param("id") Long id);
+}

+ 77 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterLearningEventMapper.java

@@ -0,0 +1,77 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterLearningEvent;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+/**
+ * lobster_learning_events 聚合查询 Mapper
+ * 复杂聚合分析使用原生@Select,简单CRUD继承BaseMapper
+ */
+@Mapper
+public interface LobsterLearningEventMapper extends BaseMapper<LobsterLearningEvent> {
+
+    @Select("SELECT DISTINCT company_id FROM lobster_learning_events WHERE analyzed = 0")
+    List<Long> selectActiveCompanyIds();
+
+    @Select("SELECT COUNT(*) FROM lobster_learning_events WHERE company_id = #{companyId} AND analyzed = 0")
+    int countUnanalyzed(@Param("companyId") Long companyId);
+
+    @Update("UPDATE lobster_learning_events SET analyzed = 1 WHERE company_id = #{companyId} AND analyzed = 0")
+    int markAnalyzed(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_learning_events WHERE company_id = #{companyId}")
+    int countByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT ROUND(SUM(CASE WHEN outcome IN ('replied','positive','converted') THEN 1 ELSE 0 END) " +
+            "* 100.0 / COUNT(*), 1) FROM lobster_learning_events WHERE company_id = #{companyId}")
+    Double overallReplyRate(@Param("companyId") Long companyId);
+
+    /** 话术效果分析 - 低效话术 TOP10 */
+    @Select("SELECT node_code, sent_message, COUNT(*) as total, " +
+            "SUM(CASE WHEN outcome = 'replied' OR outcome = 'positive' THEN 1 ELSE 0 END) as replied, " +
+            "ROUND(SUM(CASE WHEN outcome = 'replied' OR outcome = 'positive' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as reply_rate " +
+            "FROM lobster_learning_events " +
+            "WHERE company_id = #{companyId} AND analyzed = 0 AND sent_message IS NOT NULL " +
+            "GROUP BY node_code, sent_message HAVING COUNT(*) >= 3 " +
+            "ORDER BY reply_rate ASC LIMIT 10")
+    @MapKey("node_code")
+    java.util.List<java.util.Map<String, Object>> analyzeLowPerformers(@Param("companyId") Long companyId);
+
+    /** 时机分析 - 按小时统计回复率 */
+    @Select("SELECT send_hour, COUNT(*) as total, " +
+            "SUM(CASE WHEN outcome IN ('replied','positive','converted') THEN 1 ELSE 0 END) as replied, " +
+            "ROUND(SUM(CASE WHEN outcome IN ('replied','positive','converted') THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as reply_rate " +
+            "FROM lobster_learning_events " +
+            "WHERE company_id = #{companyId} AND analyzed = 0 AND send_hour IS NOT NULL " +
+            "GROUP BY send_hour HAVING COUNT(*) >= 5 ORDER BY reply_rate DESC")
+    java.util.List<java.util.Map<String, Object>> analyzeTiming(@Param("companyId") Long companyId);
+
+    /** 流程瓶颈分析 - 未回复率 TOP5 */
+    @Select("SELECT workflow_id, node_code, node_type, COUNT(*) as total, " +
+            "SUM(CASE WHEN outcome = 'no_reply' THEN 1 ELSE 0 END) as no_reply_count, " +
+            "ROUND(SUM(CASE WHEN outcome = 'no_reply' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as no_reply_rate " +
+            "FROM lobster_learning_events " +
+            "WHERE company_id = #{companyId} AND analyzed = 0 " +
+            "GROUP BY workflow_id, node_code, node_type HAVING COUNT(*) >= 5 AND no_reply_rate > 60 " +
+            "ORDER BY no_reply_rate DESC LIMIT 5")
+    java.util.List<java.util.Map<String, Object>> analyzeBottlenecks(@Param("companyId") Long companyId);
+
+    /** 渠道效果分析 */
+    @Select("SELECT channel_type, COUNT(*) as total, " +
+            "SUM(CASE WHEN outcome IN ('replied','positive','converted') THEN 1 ELSE 0 END) as replied, " +
+            "ROUND(SUM(CASE WHEN outcome IN ('replied','positive','converted') THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 1) as reply_rate " +
+            "FROM lobster_learning_events " +
+            "WHERE company_id = #{companyId} AND analyzed = 0 " +
+            "GROUP BY channel_type HAVING COUNT(*) >= 5 ORDER BY reply_rate DESC")
+    java.util.List<java.util.Map<String, Object>> analyzeChannel(@Param("companyId") Long companyId);
+
+    /** 策略推荐 TOP3 */
+    @Select("SELECT node_code, sent_message, " +
+            "ROUND(AVG(CASE WHEN outcome IN ('replied','positive') THEN 1 ELSE 0 END) * 100, 1) as avg_reply_rate " +
+            "FROM lobster_learning_events WHERE company_id = #{companyId} " +
+            "GROUP BY node_code, sent_message HAVING COUNT(*) >= 5 ORDER BY avg_reply_rate DESC LIMIT 3")
+    java.util.List<java.util.Map<String, Object>> topPerformers(@Param("companyId") Long companyId);
+}

+ 18 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterLearningPatternMapper.java

@@ -0,0 +1,18 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterLearningPattern;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterLearningPatternMapper extends BaseMapper<LobsterLearningPattern> {
+
+    @Select("SELECT * FROM lobster_learning_patterns WHERE company_id = #{companyId} AND scenario = #{scenario} " +
+            "ORDER BY confidence DESC LIMIT 5")
+    List<LobsterLearningPattern> selectByScenario(@Param("companyId") Long companyId, @Param("scenario") String scenario);
+
+    @Select("SELECT COUNT(*) FROM lobster_learning_patterns WHERE company_id = #{companyId} AND scenario = #{scenario}")
+    int countByScenario(@Param("companyId") Long companyId, @Param("scenario") String scenario);
+}

+ 26 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterLearningResultMapper.java

@@ -0,0 +1,26 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterLearningResult;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterLearningResultMapper extends BaseMapper<LobsterLearningResult> {
+
+    @Select("SELECT * FROM lobster_learning_results WHERE company_id = #{companyId} ORDER BY discovered_at DESC LIMIT 50")
+    List<LobsterLearningResult> selectByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_learning_results WHERE id = #{id} AND company_id = #{companyId} AND status = 'discovered'")
+    LobsterLearningResult selectByIdAndStatus(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_learning_results WHERE company_id = #{companyId}")
+    int countByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_learning_results WHERE company_id = #{companyId} AND status = #{status}")
+    int countByStatus(@Param("companyId") Long companyId, @Param("status") String status);
+
+    @Update("UPDATE lobster_learning_results SET status = 'applied', applied_at = NOW() WHERE id = #{id}")
+    int markApplied(@Param("id") Long id);
+}

+ 9 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterMessageVariantMapper.java

@@ -0,0 +1,9 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterMessageVariant;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LobsterMessageVariantMapper extends BaseMapper<LobsterMessageVariant> {
+}

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

@@ -0,0 +1,27 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterNodeOptimizationConfig;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterNodeOptimizationConfigMapper extends BaseMapper<LobsterNodeOptimizationConfig> {
+
+    @Select("SELECT enabled FROM lobster_node_optimization_config " +
+            "WHERE company_id = #{companyId} AND workflow_id = #{workflowId} AND node_code = #{nodeCode}")
+    Boolean findEnabled(@Param("companyId") Long companyId,
+                        @Param("workflowId") Long workflowId,
+                        @Param("nodeCode") String nodeCode);
+
+    @Select("SELECT * FROM lobster_node_optimization_config " +
+            "WHERE company_id = #{companyId} AND workflow_id = #{workflowId} AND node_code = #{nodeCode}")
+    LobsterNodeOptimizationConfig selectByKey(@Param("companyId") Long companyId,
+                                              @Param("workflowId") Long workflowId,
+                                              @Param("nodeCode") String nodeCode);
+
+    @Insert("INSERT INTO lobster_node_optimization_config " +
+            "(company_id, workflow_id, node_code, enabled, auto_apply, config_by, update_time) " +
+            "VALUES (#{companyId}, #{workflowId}, #{nodeCode}, #{enabled}, #{autoApply}, #{configBy}, NOW()) " +
+            "ON DUPLICATE KEY UPDATE enabled = #{enabled}, auto_apply = #{autoApply}, config_by = #{configBy}, update_time = NOW()")
+    int upsert(LobsterNodeOptimizationConfig entity);
+}

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

@@ -0,0 +1,32 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterPendingKnowledge;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterPendingKnowledgeMapper extends BaseMapper<LobsterPendingKnowledge> {
+
+    @Select("SELECT * FROM lobster_pending_knowledge WHERE company_id = #{companyId} AND audit_status = 'pending'")
+    List<LobsterPendingKnowledge> selectPendingByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_pending_knowledge WHERE company_id = #{companyId} AND audit_status = 'pending'")
+    int countPendingByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_pending_knowledge WHERE id = #{id} AND company_id = #{companyId}")
+    LobsterPendingKnowledge selectByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    @Update("UPDATE lobster_pending_knowledge SET question = #{question}, answer = #{answer}, update_by = #{updateBy}, update_time = NOW() WHERE id = #{id} AND company_id = #{companyId}")
+    int updateContent(@Param("id") Long id, @Param("companyId") Long companyId, @Param("question") String question, @Param("answer") String answer, @Param("updateBy") String updateBy);
+
+    @Update("UPDATE lobster_pending_knowledge SET audit_status = 'approved', auditor = #{auditor}, audit_time = NOW(), update_time = NOW() WHERE id = #{id} AND company_id = #{companyId}")
+    int approve(@Param("id") Long id, @Param("companyId") Long companyId, @Param("auditor") String auditor);
+
+    @Update("UPDATE lobster_pending_knowledge SET audit_status = 'rejected', audit_comment = #{comment}, auditor = #{auditor}, audit_time = NOW(), update_time = NOW() WHERE id = #{id} AND company_id = #{companyId} AND audit_status = 'pending'")
+    int reject(@Param("id") Long id, @Param("companyId") Long companyId, @Param("auditor") String auditor, @Param("comment") String comment);
+
+    @Delete("DELETE FROM lobster_pending_knowledge WHERE id = #{id} AND company_id = #{companyId}")
+    int deleteByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
+}

+ 35 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterPromptConfigMapper.java

@@ -0,0 +1,35 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterPromptConfig;
+import org.apache.ibatis.annotations.*;
+import java.util.List;
+
+@Mapper
+public interface LobsterPromptConfigMapper extends BaseMapper<LobsterPromptConfig> {
+
+    @Select("SELECT content FROM lobster_prompt_config"
+            + " WHERE company_id = #{companyId} AND prompt_type = #{promptType}"
+            + " AND scope = #{scope} AND deleted = 0"
+            + " AND workflow_code = #{workflowCode} AND node_code = #{nodeCode}"
+            + " LIMIT 1")
+    String selectContent(@Param("companyId") Long companyId,
+                         @Param("promptType") String promptType,
+                         @Param("scope") String scope,
+                         @Param("workflowCode") String workflowCode,
+                         @Param("nodeCode") String nodeCode);
+
+    @Select("SELECT content FROM lobster_prompt_config"
+            + " WHERE company_id = #{companyId} AND prompt_type = #{promptType}"
+            + " AND scope = #{scope} AND deleted = 0"
+            + " LIMIT 1")
+    String selectGlobalContent(@Param("companyId") Long companyId,
+                               @Param("promptType") String promptType,
+                               @Param("scope") String scope);
+
+    @Select("SELECT id, company_id, workflow_code, node_code, prompt_type, content, scope, update_time"
+            + " FROM lobster_prompt_config"
+            + " WHERE company_id = #{companyId} AND deleted = 0"
+            + " ORDER BY scope, workflow_code, node_code, prompt_type")
+    List<LobsterPromptConfig> selectByCompanyId(@Param("companyId") Long companyId);
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterSegmentMessageOverrideMapper.java

@@ -0,0 +1,15 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterSegmentMessageOverride;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterSegmentMessageOverrideMapper extends BaseMapper<LobsterSegmentMessageOverride> {
+
+    @Select("SELECT message_content FROM lobster_segment_message_override " +
+            "WHERE company_id = #{companyId} AND node_code = #{nodeCode} AND segment_code = #{segmentCode} AND deleted = 0")
+    String selectMessageByNodeAndSegment(@Param("companyId") Long companyId,
+                                         @Param("nodeCode") String nodeCode,
+                                         @Param("segmentCode") String segmentCode);
+}

+ 21 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterSegmentRuleMapper.java

@@ -0,0 +1,21 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterSegmentRule;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterSegmentRuleMapper extends BaseMapper<LobsterSegmentRule> {
+
+    @Select("SELECT * FROM lobster_segment_rule WHERE company_id = #{companyId} ORDER BY priority DESC")
+    List<LobsterSegmentRule> selectByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT segment_code, condition_expr, priority FROM lobster_segment_rule " +
+            "WHERE company_id = #{companyId} AND enabled = 1 ORDER BY priority DESC")
+    List<LobsterSegmentRule> selectEnabledByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_segment_rule WHERE company_id = #{companyId} AND segment_code = #{segmentCode}")
+    List<LobsterSegmentRule> selectBySegmentCode(@Param("companyId") Long companyId, @Param("segmentCode") String segmentCode);
+}

+ 16 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterSensitiveWordMapper.java

@@ -0,0 +1,16 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterSensitiveWord;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterSensitiveWordMapper extends BaseMapper<LobsterSensitiveWord> {
+
+    List<LobsterSensitiveWord> selectByCompanyId(@Param("companyId") Long companyId);
+
+    int softDeleteById(@Param("id") Long id);
+}

+ 5 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterSystemPromptMapper.java

@@ -26,4 +26,9 @@ public interface LobsterSystemPromptMapper {
     int softDeleteById(@Param("id") Long id);
 
     List<String> selectCategories();
+
+    /**
+     * 查询所有启用的提示词(用于缓存全量加载)
+     */
+    List<LobsterSystemPrompt> selectEnabledOrdered();
 }

+ 14 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterTenantKeywordMapper.java

@@ -0,0 +1,14 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterTenantKeyword;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterTenantKeywordMapper extends BaseMapper<LobsterTenantKeyword> {
+
+    @Select("SELECT * FROM lobster_tenant_keywords WHERE company_id = #{companyId} AND enabled = 1")
+    List<LobsterTenantKeyword> selectEnabledByCompanyId(@Param("companyId") Long companyId);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterToolConfigMapper.java

@@ -0,0 +1,25 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterToolConfig;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterToolConfigMapper extends BaseMapper<LobsterToolConfig> {
+
+    @Select("SELECT tool_name, tool_type, description FROM lobster_tool_config " +
+            "WHERE (company_id = #{companyId} OR company_id = 0) AND enabled = 1 AND deleted = 0 ORDER BY tool_name")
+    List<LobsterToolConfig> selectEnabled(@Param("companyId") Long companyId);
+
+    @Select("SELECT tool_name, tool_type, config_json FROM lobster_tool_config " +
+            "WHERE tool_name = #{toolName} AND (company_id = #{companyId} OR company_id = 0) " +
+            "AND enabled = 1 AND deleted = 0 ORDER BY company_id DESC LIMIT 1")
+    LobsterToolConfig selectByCompanyAndName(@Param("companyId") Long companyId, @Param("toolName") String toolName);
+
+    @Insert("INSERT INTO lobster_tool_config (company_id, tool_name, tool_type, config_json, enabled, create_time, deleted) " +
+            "VALUES (#{companyId}, #{toolName}, #{toolType}, #{configJson}, #{enabled}, NOW(), 0) " +
+            "ON DUPLICATE KEY UPDATE tool_type = #{toolType}, config_json = #{configJson}, enabled = 1")
+    int upsert(LobsterToolConfig entity);
+}

+ 15 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterUserEventMapper.java

@@ -0,0 +1,15 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserEvent;
+import org.apache.ibatis.annotations.*;
+
+@Mapper
+public interface LobsterUserEventMapper extends BaseMapper<LobsterUserEvent> {
+
+    @Update("UPDATE lobster_user_events SET process_status = #{status} WHERE contact_id = #{contactId}")
+    int updateProcessStatus(@Param("contactId") String contactId, @Param("status") String status);
+
+    @Select("SELECT COUNT(*) FROM lobster_user_events WHERE company_id = #{companyId} AND event_type = #{eventType}")
+    Long countByType(@Param("companyId") Long companyId, @Param("eventType") String eventType);
+}

+ 46 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterUserNodeInteractionMapper.java

@@ -0,0 +1,46 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserNodeInteraction;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface LobsterUserNodeInteractionMapper extends BaseMapper<LobsterUserNodeInteraction> {
+
+    /** 查找需要优化的节点 */
+    @Select("SELECT workflow_id, node_code, sent_message, " +
+            "COUNT(*) as total, " +
+            "SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply_count, " +
+            "ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate, " +
+            "AVG(sentiment) as avg_sentiment, " +
+            "AVG(reply_delay_ms) as avg_delay_ms " +
+            "FROM lobster_user_node_interaction " +
+            "WHERE company_id = #{companyId} AND external_user_id = #{externalUserId} " +
+            "AND workflow_id = #{workflowId} " +
+            "GROUP BY workflow_id, node_code, sent_message " +
+            "HAVING COUNT(*) >= #{threshold} AND (no_reply_rate > 50 OR avg_sentiment < -0.3) " +
+            "ORDER BY no_reply_rate DESC LIMIT 10")
+    List<Map<String, Object>> findProblemNodes(@Param("companyId") Long companyId,
+                                                @Param("externalUserId") String externalUserId,
+                                                @Param("workflowId") Long workflowId,
+                                                @Param("threshold") int threshold);
+
+    /** 查找需要优化的节点(无workflowId过滤) */
+    @Select("SELECT workflow_id, node_code, sent_message, " +
+            "COUNT(*) as total, " +
+            "SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply_count, " +
+            "ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate, " +
+            "AVG(sentiment) as avg_sentiment, " +
+            "AVG(reply_delay_ms) as avg_delay_ms " +
+            "FROM lobster_user_node_interaction " +
+            "WHERE company_id = #{companyId} AND external_user_id = #{externalUserId} " +
+            "GROUP BY workflow_id, node_code, sent_message " +
+            "HAVING COUNT(*) >= #{threshold} AND (no_reply_rate > 50 OR avg_sentiment < -0.3) " +
+            "ORDER BY no_reply_rate DESC LIMIT 10")
+    List<Map<String, Object>> findProblemNodesAll(@Param("companyId") Long companyId,
+                                                   @Param("externalUserId") String externalUserId,
+                                                   @Param("threshold") int threshold);
+}

+ 53 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterUserNodeOptimizationMapper.java

@@ -0,0 +1,53 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserNodeOptimization;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface LobsterUserNodeOptimizationMapper extends BaseMapper<LobsterUserNodeOptimization> {
+
+    @Select("SELECT optimized_content FROM lobster_user_node_optimization " +
+            "WHERE company_id = #{companyId} AND external_user_id = #{externalUserId} " +
+            "AND node_code = #{nodeCode} AND status = 'applied' ORDER BY apply_time DESC LIMIT 1")
+    String findAppliedContent(@Param("companyId") Long companyId,
+                              @Param("externalUserId") String externalUserId,
+                              @Param("nodeCode") String nodeCode);
+
+    @Select("SELECT * FROM lobster_user_node_optimization " +
+            "WHERE id = #{id} AND company_id = #{companyId} AND status = 'pending'")
+    LobsterUserNodeOptimization selectPendingById(@Param("id") Long id, @Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_user_node_optimization " +
+            "WHERE company_id = #{companyId} AND status IN ('pending','pending_audit') " +
+            "ORDER BY confidence DESC, create_time DESC LIMIT #{limit} OFFSET #{offset}")
+    List<LobsterUserNodeOptimization> selectPendingAudit(@Param("companyId") Long companyId,
+                                                          @Param("limit") int limit,
+                                                          @Param("offset") int offset);
+
+    @Update("UPDATE lobster_user_node_optimization SET status = 'applied', apply_time = NOW() WHERE id = #{id}")
+    int markApplied(@Param("id") Long id);
+
+    @Update("UPDATE lobster_user_node_optimization SET status = 'pending_audit' WHERE id = #{id}")
+    int markPendingAudit(@Param("id") Long id);
+
+    @Update("UPDATE lobster_user_node_optimization SET status = #{status}, auditor_id = #{auditorId}, " +
+            "audit_remark = #{auditRemark}, audit_time = NOW(), apply_time = CASE WHEN #{status} = 'applied' THEN NOW() ELSE NULL END " +
+            "WHERE id = #{id} AND company_id = #{companyId} AND status IN ('pending','pending_audit')")
+    int audit(@Param("id") Long id, @Param("companyId") Long companyId,
+              @Param("status") String status, @Param("auditorId") String auditorId,
+              @Param("auditRemark") String auditRemark);
+
+    @Select("SELECT COUNT(*) FROM lobster_user_node_optimization WHERE company_id = #{companyId}")
+    int countByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT COUNT(*) FROM lobster_user_node_optimization WHERE company_id = #{companyId} AND status = #{status}")
+    int countByStatus(@Param("companyId") Long companyId, @Param("status") String status);
+
+    @Select("SELECT optimization_type, COUNT(*) as count FROM lobster_user_node_optimization " +
+            "WHERE company_id = #{companyId} GROUP BY optimization_type")
+    List<Map<String, Object>> countGroupByType(@Param("companyId") Long companyId);
+}

+ 22 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterUserPreferenceMapper.java

@@ -0,0 +1,22 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserPreference;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterUserPreferenceMapper extends BaseMapper<LobsterUserPreference> {
+
+    @Select("SELECT * FROM lobster_user_preference WHERE company_id = #{companyId} AND external_user_id = #{externalUserId}")
+    List<LobsterUserPreference> selectByUserId(@Param("companyId") Long companyId, @Param("externalUserId") String externalUserId);
+
+    @Select("SELECT * FROM lobster_user_preference WHERE company_id = #{companyId} AND external_user_id = #{externalUserId} AND preference_type = #{preferenceType}")
+    LobsterUserPreference selectByUserIdAndType(@Param("companyId") Long companyId, @Param("externalUserId") String externalUserId, @Param("preferenceType") String preferenceType);
+
+    @Insert("INSERT INTO lobster_user_preference (company_id, external_user_id, preference_type, preference_value, update_time) " +
+            "VALUES (#{companyId}, #{externalUserId}, #{preferenceType}, #{preferenceValue}, NOW()) " +
+            "ON DUPLICATE KEY UPDATE preference_value = #{preferenceValue}, update_time = NOW()")
+    int upsert(LobsterUserPreference entity);
+}

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

@@ -0,0 +1,17 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserSegment;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterUserSegmentMapper extends BaseMapper<LobsterUserSegment> {
+
+    @Select("SELECT * FROM lobster_user_segment WHERE company_id = #{companyId}")
+    List<LobsterUserSegment> selectByCompanyId(@Param("companyId") Long companyId);
+
+    @Select("SELECT * FROM lobster_user_segment WHERE company_id = #{companyId} AND segment_code = #{segmentCode} AND deleted = 0")
+    LobsterUserSegment selectByCompanyAndSegmentCode(@Param("companyId") Long companyId, @Param("segmentCode") String segmentCode);
+}

+ 25 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterUserSegmentRelMapper.java

@@ -0,0 +1,25 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterUserSegmentRel;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterUserSegmentRelMapper extends BaseMapper<LobsterUserSegmentRel> {
+
+    @Select("SELECT * FROM lobster_user_segment_rel WHERE company_id = #{companyId} AND external_user_id = #{externalUserId}")
+    List<LobsterUserSegmentRel> selectByUserId(@Param("companyId") Long companyId, @Param("externalUserId") String externalUserId);
+
+    @Select("SELECT external_user_id FROM lobster_user_segment_rel WHERE company_id = #{companyId} AND segment_code = #{segmentCode}")
+    List<String> selectUserIdsBySegmentCode(@Param("companyId") Long companyId, @Param("segmentCode") String segmentCode);
+
+    @Select("SELECT segment_code FROM lobster_user_segment_rel WHERE company_id = #{companyId} AND external_user_id = #{externalUserId} AND status = 1")
+    List<String> selectSegmentCodesByUserId(@Param("companyId") Long companyId, @Param("externalUserId") String externalUserId);
+
+    @Insert("INSERT INTO lobster_user_segment_rel (company_id, external_user_id, segment_code, status, create_time, update_time) " +
+            "VALUES (#{companyId}, #{externalUserId}, #{segmentCode}, 1, NOW(), NOW()) " +
+            "ON DUPLICATE KEY UPDATE segment_code = #{segmentCode}, status = 1, update_time = NOW()")
+    int upsert(LobsterUserSegmentRel entity);
+}

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

@@ -0,0 +1,30 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterVectorStore;
+import org.apache.ibatis.annotations.*;
+
+import java.util.List;
+
+@Mapper
+public interface LobsterVectorStoreMapper extends BaseMapper<LobsterVectorStore> {
+
+    @Select("SELECT * FROM lobster_vector_store WHERE company_id = #{companyId} AND category = #{category}")
+    List<LobsterVectorStore> selectByCompanyAndCategory(@Param("companyId") Long companyId,
+                                                        @Param("category") String category);
+
+    @Delete("DELETE FROM lobster_vector_store WHERE company_id = #{companyId} AND category = #{category} AND vec_key = #{key}")
+    int deleteByKey(@Param("companyId") Long companyId, @Param("category") String category,
+                    @Param("key") String key);
+
+    @Select("SELECT * FROM lobster_vector_store WHERE company_id = #{companyId} AND category = #{category} " +
+            "AND text_content LIKE CONCAT('%', #{keyword}, '%') LIMIT #{limit}")
+    List<LobsterVectorStore> searchByKeyword(@Param("companyId") Long companyId, @Param("category") String category,
+                                             @Param("keyword") String keyword, @Param("limit") int limit);
+
+    @Insert("INSERT INTO lobster_vector_store " +
+            "(company_id, category, vec_key, text_content, vector, metadata, create_time, update_time) " +
+            "VALUES (#{companyId}, #{category}, #{vecKey}, #{textContent}, #{vector}, #{metadata}, NOW(), NOW()) " +
+            "ON DUPLICATE KEY UPDATE text_content = #{textContent}, vector = #{vector}, metadata = #{metadata}, update_time = NOW()")
+    int upsert(LobsterVectorStore entity);
+}

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

@@ -54,4 +54,6 @@ public interface ICompanyTagTemplateBindingService {
      * 批量添加龙虾标签给企微客户
      */
     AjaxResult batchBindLobsterTag(Long companyId, String userName, String qwCorpId, List<Long> externalContactIds, List<String> tagCodes,Long companyUserId);
+
+    AjaxResult lobsterTags(List<Long> userIds, Long userId, Long companyId);
 }

+ 32 - 1
fs-service/src/main/java/com/fs/company/service/billing/BillingService.java

@@ -12,7 +12,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.Date;
+import java.util.*;
 
 /**
  * 统一计费扣费服务
@@ -236,6 +236,37 @@ public class BillingService {
         return balance.getTotalBalance().compareTo(amount) >= 0;
     }
 
+    /**
+     * 分页查询消费记录
+     */
+    public List<Map<String, Object>> getConsumeRecords(Long tenantId, int page, int size) {
+        int offset = (page - 1) * size;
+        TenantConsumeRecord query = new TenantConsumeRecord();
+        query.setTenantId(tenantId);
+        List<TenantConsumeRecord> list = consumeRecordMapper.selectTenantConsumeRecordList(query);
+        // 手动分页 (Mapper 未支持分页参数)
+        List<Map<String, Object>> result = new java.util.ArrayList<>();
+        for (int i = offset; i < Math.min(offset + size, list.size()); i++) {
+            TenantConsumeRecord r = list.get(i);
+            Map<String, Object> m = new java.util.LinkedHashMap<>();
+            m.put("recordId", r.getRecordId());
+            m.put("tenantId", r.getTenantId());
+            m.put("consumeType", r.getConsumeType());
+            m.put("amount", r.getAmount());
+            m.put("remark", r.getRemark());
+            m.put("status", r.getStatus());
+            m.put("consumeTime", r.getConsumeTime());
+            result.add(m);
+        }
+        return result;
+    }
+
+    public long countConsumeRecords(Long tenantId) {
+        TenantConsumeRecord query = new TenantConsumeRecord();
+        query.setTenantId(tenantId);
+        return consumeRecordMapper.selectTenantConsumeRecordList(query).size();
+    }
+
     /** 消费类型名称 */
     public static String getConsumeTypeName(int type) {
         return type >= 1 && type < CONSUME_TYPE_NAMES.length ? CONSUME_TYPE_NAMES[type] : "未知";

+ 6 - 0
fs-service/src/main/java/com/fs/company/service/impl/CompanyTagTemplateBindingServiceImpl.java

@@ -409,6 +409,12 @@ public class CompanyTagTemplateBindingServiceImpl implements ICompanyTagTemplate
         return AjaxResult.success("已为 " + externalContactIds.size() + " 个客户添加 " + targetBindings.size() + " 个龙虾标签");
     }
 
+    @Override
+    public AjaxResult lobsterTags(List<Long> userIds, Long userId, Long companyId) {
+        return AjaxResult.success(companyLobsterTagUserRelMapper.selectLobsterTagsByExId( userIds,  userId,  companyId));
+
+    }
+
     private LocalDateTime getSendTimeNode(CompanyWorkflowLobsterNode node) {
         Integer days = Integer.valueOf(node.getNodeCode().substring(4));
         LocalDate date = LocalDate.now().plusDays(days);

+ 20 - 0
fs-service/src/main/java/com/fs/company/service/workflow/ILobsterEventAuditService.java

@@ -0,0 +1,20 @@
+package com.fs.company.service.workflow;
+
+import com.fs.company.domain.LobsterEventAudit;
+
+import java.util.List;
+
+public interface ILobsterEventAuditService {
+
+    List<LobsterEventAudit> selectListByCompanyIdAndStatus(Long companyId, String status, int page, int size);
+
+    long countByCompanyIdAndStatus(Long companyId, String status);
+
+    LobsterEventAudit selectByIdAndStatus(Long id, String status);
+
+    void approve(Long id, String auditBy, String auditComment);
+
+    void reject(Long id, String auditBy, String auditComment);
+
+    LobsterEventAudit selectById(Long id);
+}

+ 10 - 0
fs-service/src/main/java/com/fs/company/service/workflow/ILobsterLearningCorpusService.java

@@ -0,0 +1,10 @@
+package com.fs.company.service.workflow;
+
+import com.fs.company.domain.LobsterLearningCorpus;
+
+import java.util.List;
+
+public interface ILobsterLearningCorpusService {
+
+    List<LobsterLearningCorpus> selectListByCompanyId(Long companyId, String scenario, String status);
+}

+ 6 - 0
fs-service/src/main/java/com/fs/company/service/workflow/PendingAuditKnowledgeService.java

@@ -138,6 +138,8 @@ public interface PendingAuditKnowledgeService {
         private String auditComment;
         private String auditor;
         private String auditTime;
+        private String createBy;
+        private String updateBy;
         private String createTime;
         private String updateTime;
 
@@ -168,6 +170,10 @@ public interface PendingAuditKnowledgeService {
         public void setAuditTime(String auditTime) { this.auditTime = auditTime; }
         public String getCreateTime() { return createTime; }
         public void setCreateTime(String createTime) { this.createTime = createTime; }
+        public String getCreateBy() { return createBy; }
+        public void setCreateBy(String createBy) { this.createBy = createBy; }
+        public String getUpdateBy() { return updateBy; }
+        public void setUpdateBy(String updateBy) { this.updateBy = updateBy; }
         public String getUpdateTime() { return updateTime; }
         public void setUpdateTime(String updateTime) { this.updateTime = updateTime; }
     }

+ 46 - 60
fs-service/src/main/java/com/fs/company/service/workflow/api/ApiRegistryService.java

@@ -2,10 +2,12 @@ package com.fs.company.service.workflow.api;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.company.domain.LobsterApiRegistry;
+import com.fs.company.mapper.LobsterApiRegistryMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
@@ -28,10 +30,8 @@ public class ApiRegistryService {
 
     private static final Logger log = LoggerFactory.getLogger(ApiRegistryService.class);
 
-    private static final String TABLE_NAME = "lobster_api_registry";
-
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterApiRegistryMapper apiRegistryMapper;
 
     /** 缓存: key → ApiEndpoint */
     private final ConcurrentHashMap<String, ApiEndpoint> cache = new ConcurrentHashMap<>();
@@ -43,24 +43,22 @@ public class ApiRegistryService {
 
     public void refreshCache() {
         try {
-            ensureTable();
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(
-                    "SELECT * FROM " + TABLE_NAME + " WHERE enabled=1 ORDER BY priority ASC");
+            List<LobsterApiRegistry> rows = apiRegistryMapper.selectEnabled();
             cache.clear();
-            for (Map<String, Object> row : rows) {
-                String key = row.get("api_key") + "|" + row.get("provider");
+            for (LobsterApiRegistry row : rows) {
+                String key = row.getApiKey() + "|" + row.getProvider();
                 ApiEndpoint ep = new ApiEndpoint();
-                ep.apiKey = (String) row.get("api_key");
-                ep.apiName = (String) row.get("api_name");
-                ep.category = (String) row.get("category");
-                ep.provider = (String) row.get("provider");
-                ep.baseUrl = (String) row.get("base_url");
-                ep.authType = (String) row.get("auth_type");
-                ep.authConfig = (String) row.get("auth_config");
-                ep.timeout = row.get("timeout") != null ? ((Number) row.get("timeout")).intValue() : 5000;
-                ep.priority = row.get("priority") != null ? ((Number) row.get("priority")).intValue() : 0;
-                ep.isBackup = row.get("is_backup") != null && ((Number) row.get("is_backup")).intValue() == 1;
-                ep.description = (String) row.get("description");
+                ep.apiKey = row.getApiKey();
+                ep.apiName = row.getApiName();
+                ep.category = row.getCategory();
+                ep.provider = row.getProvider();
+                ep.baseUrl = row.getBaseUrl();
+                ep.authType = row.getAuthType();
+                ep.authConfig = row.getAuthConfig();
+                ep.timeout = row.getTimeout() != null ? row.getTimeout() : 5000;
+                ep.priority = row.getPriority() != null ? row.getPriority() : 0;
+                ep.isBackup = row.getIsBackup() != null && row.getIsBackup() == 1;
+                ep.description = row.getDescription();
                 cache.put(key, ep);
             }
             log.info("[ApiRegistry] 加载了 {} 个已注册接口", cache.size());
@@ -88,7 +86,6 @@ public class ApiRegistryService {
                 return ep;
             }
         }
-        // 备用接口不可用时返回主接口
         if (useBackup) return get(apiKey, false);
         return null;
     }
@@ -119,17 +116,34 @@ public class ApiRegistryService {
      */
     public void register(ApiEndpoint ep) {
         try {
-            ensureTable();
-            jdbcTemplate.update(
-                    "INSERT INTO " + TABLE_NAME + " (api_key, api_name, category, provider, " +
-                    "base_url, auth_type, auth_config, timeout, priority, is_backup, " +
-                    "description, enabled, create_time) " +
-                    "VALUES (?,?,?,?,?,?,?,?,?,?,?,1,NOW()) " +
-                    "ON DUPLICATE KEY UPDATE base_url=VALUES(base_url), auth_config=VALUES(auth_config), " +
-                    "update_time=NOW()",
-                    ep.apiKey, ep.apiName, ep.category, ep.provider,
-                    ep.baseUrl, ep.authType, ep.authConfig, ep.timeout, ep.priority,
-                    ep.isBackup ? 1 : 0, ep.description);
+            // 尝试查找已存在的同api_key+provider记录
+            LambdaQueryWrapper<LobsterApiRegistry> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterApiRegistry::getApiKey, ep.apiKey)
+                   .eq(LobsterApiRegistry::getProvider, ep.provider);
+            LobsterApiRegistry existing = apiRegistryMapper.selectOne(wrapper);
+
+            LobsterApiRegistry entity = new LobsterApiRegistry();
+            entity.setApiKey(ep.apiKey);
+            entity.setApiName(ep.apiName);
+            entity.setCategory(ep.category);
+            entity.setProvider(ep.provider);
+            entity.setBaseUrl(ep.baseUrl);
+            entity.setAuthType(ep.authType);
+            entity.setAuthConfig(ep.authConfig);
+            entity.setTimeout(ep.timeout);
+            entity.setPriority(ep.priority);
+            entity.setIsBackup(ep.isBackup ? 1 : 0);
+            entity.setDescription(ep.description);
+
+            if (existing != null) {
+                entity.setId(existing.getId());
+                entity.setUpdateTime(java.time.LocalDateTime.now());
+                apiRegistryMapper.updateById(entity);
+            } else {
+                entity.setEnabled(1);
+                entity.setCreateTime(java.time.LocalDateTime.now());
+                apiRegistryMapper.insert(entity);
+            }
             refreshCache();
         } catch (Exception e) {
             log.warn("[ApiRegistry] 注册失败: {}", e.getMessage());
@@ -159,34 +173,6 @@ public class ApiRegistryService {
         return headers;
     }
 
-    private void ensureTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + TABLE_NAME + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "api_key VARCHAR(100) NOT NULL COMMENT '接口标识', " +
-                    "api_name VARCHAR(200) COMMENT '接口名称', " +
-                    "category VARCHAR(50) COMMENT '分类: webhook/internal/ai/third-party', " +
-                    "provider VARCHAR(100) COMMENT '服务商', " +
-                    "base_url VARCHAR(500) COMMENT '基础URL', " +
-                    "auth_type VARCHAR(50) COMMENT '认证类型: bearer/api-key/basic', " +
-                    "auth_config TEXT COMMENT '认证配置JSON', " +
-                    "timeout INT DEFAULT 5000 COMMENT '超时ms', " +
-                    "priority INT DEFAULT 0 COMMENT '优先级', " +
-                    "is_backup TINYINT DEFAULT 0 COMMENT '是否备用接口', " +
-                    "description VARCHAR(500) COMMENT '描述', " +
-                    "enabled TINYINT DEFAULT 1 COMMENT '1启用0禁用', " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "update_time DATETIME DEFAULT NOW() ON UPDATE NOW(), " +
-                    "UNIQUE KEY uk_api_provider (api_key, provider), " +
-                    "INDEX idx_category (category)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            log.info("[ApiRegistry] 自动创建了{}表", TABLE_NAME);
-        }
-    }
-
     /* ========== 数据模型 ========== */
 
     public static class ApiEndpoint {

+ 10 - 78
fs-service/src/main/java/com/fs/company/service/workflow/channel/ChannelTypeRegistry.java

@@ -1,5 +1,8 @@
 package com.fs.company.service.workflow.channel;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.company.domain.LobsterChannelTypeRegistry;
+import com.fs.company.mapper.LobsterChannelTypeRegistryMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -10,31 +13,13 @@ import javax.annotation.PostConstruct;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
-/**
- * 渠道类型注册中心 —— 即插即用的多通道扩展机制
- *
- * 核心思路:
- *   龙虾引擎不关心用户来自哪个平台(企微/个微/WhatsApp/APP IM/Line/Telegram...)
- *   统一通过 lobster_unified_contact 表作为桥梁
- *   每个渠道只需注册: channelType + sourceTable + userIdColumn + 如何绑定到 unified_contact
- *
- * 表: lobster_channel_type_registry
- * 已注册渠道:
- *   QW      → qw_user         → qw_user_id
- *   WX      → company_wx_user → wx_account_id
- *   IM      → im_user         → im_user_id
- *   WHATSAPP → whatsapp_user  → whatsapp_id
- *   LINE     → line_user      → line_user_id
- *   TELEGRAM → telegram_user  → telegram_id
- *   APP_IM   → app_im_user    → user_id
- *   OTHER    → (自定义)
- */
 @Service
 public class ChannelTypeRegistry {
 
     private static final Logger log = LoggerFactory.getLogger(ChannelTypeRegistry.class);
 
-    private static final String REGISTRY_TABLE = "lobster_channel_type_registry";
+    @Autowired
+    private LobsterChannelTypeRegistryMapper channelTypeRegistryMapper;
 
     @Autowired(required = false)
     private JdbcTemplate jdbcTemplate;
@@ -48,9 +33,6 @@ public class ChannelTypeRegistry {
         log.info("[ChannelRegistry] 已注册{}个渠道: {}", registry.size(), registry.keySet());
     }
 
-    /**
-     * 内置渠道注册
-     */
     private void registerDefaults() {
         register("QW", "qw_user", "qw_user_id", "企业微信");
         register("WX", "company_wx_user", "wx_account_id", "个人微信/公众号");
@@ -62,9 +44,6 @@ public class ChannelTypeRegistry {
         register("OTHER", "custom_contact", "external_id", "自定义渠道");
     }
 
-    /**
-     * 注册渠道
-     */
     public void register(String channelType, String sourceTable, String userIdColumn, String displayName) {
         ChannelTypeConfig cfg = new ChannelTypeConfig();
         cfg.channelType = channelType;
@@ -74,19 +53,10 @@ public class ChannelTypeRegistry {
         registry.put(channelType, cfg);
     }
 
-    /**
-     * 根据channelType获取渠道原始用户ID
-     * 从各平台用户表中查询原始ID, 然后通过 lobster_unified_contact 建立映射
-     *
-     * @param channelType    渠道类型 QW/WX/IM/WHATSAPP/...
-     * @param contactId      龙虾统一触点ID (lobster_unified_contact.id)
-     * @param companyId      租户ID
-     */
     public String resolveSourceUserId(String channelType, Long contactId, Long companyId) {
         ChannelTypeConfig cfg = getConfig(channelType);
         if (cfg == null || jdbcTemplate == null) return null;
 
-        // 优先从 lobster_unified_contact 查 channel_user_id
         try {
             Map<String, Object> contact = jdbcTemplate.queryForMap(
                     "SELECT channel_user_id FROM lobster_unified_contact " +
@@ -96,7 +66,6 @@ public class ChannelTypeRegistry {
             if (channelUserId != null && !channelUserId.isEmpty()) return channelUserId;
         } catch (Exception ignored) {}
 
-        // 兜底: 从各平台用户表查
         try {
             String sql = "SELECT " + cfg.userIdColumn + " FROM " + cfg.sourceTable +
                     " WHERE company_id=? LIMIT 1";
@@ -107,9 +76,6 @@ public class ChannelTypeRegistry {
         return null;
     }
 
-    /**
-     * 绑定触点: 将龙虾contactId与各平台userID建立映射
-     */
     public void bindContact(Long companyId, Long contactId, String channelType, String sourceUserId) {
         if (jdbcTemplate == null) return;
         try {
@@ -122,9 +88,6 @@ public class ChannelTypeRegistry {
         }
     }
 
-    /**
-     * 获取所有已注册渠道类型(供前端下拉选择)
-     */
     public List<Map<String, String>> getAllChannelTypes() {
         List<Map<String, String>> list = new ArrayList<>();
         for (ChannelTypeConfig cfg : registry.values()) {
@@ -140,7 +103,6 @@ public class ChannelTypeRegistry {
     public ChannelTypeConfig getConfig(String channelType) {
         ChannelTypeConfig cfg = registry.get(channelType);
         if (cfg == null) {
-            // 未知渠道类型: 自动降级到 OTHER
             cfg = registry.get("OTHER");
         }
         return cfg;
@@ -150,49 +112,19 @@ public class ChannelTypeRegistry {
         return registry.containsKey(channelType);
     }
 
-    /**
-     * DB层面的渠道注册表 (保留扩展用)
-     */
     private void loadFromDb() {
-        if (jdbcTemplate == null) return;
         try {
-            ensureTable();
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(
-                    "SELECT * FROM " + REGISTRY_TABLE + " WHERE enabled=1");
-            for (Map<String, Object> r : rows) {
-                register(
-                        (String) r.get("channel_type"),
-                        (String) r.get("source_table"),
-                        (String) r.get("user_id_column"),
-                        (String) r.get("display_name")
-                );
+            LambdaQueryWrapper<LobsterChannelTypeRegistry> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterChannelTypeRegistry::getEnabled, 1);
+            List<LobsterChannelTypeRegistry> rows = channelTypeRegistryMapper.selectList(wrapper);
+            for (LobsterChannelTypeRegistry r : rows) {
+                register(r.getChannelType(), r.getSourceTable(), r.getUserIdColumn(), r.getDisplayName());
             }
         } catch (Exception e) {
             log.debug("[ChannelRegistry] DB加载渠道失败: {}", e.getMessage());
         }
     }
 
-    private void ensureTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + REGISTRY_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + REGISTRY_TABLE + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "channel_type VARCHAR(30) NOT NULL UNIQUE, " +
-                    "display_name VARCHAR(100), " +
-                    "source_table VARCHAR(100) COMMENT '平台用户表名', " +
-                    "user_id_column VARCHAR(100) COMMENT '用户ID列名', " +
-                    "enabled TINYINT DEFAULT 1, " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "update_time DATETIME DEFAULT NOW() ON UPDATE NOW()" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            log.info("[ChannelRegistry] 创建了{}表", REGISTRY_TABLE);
-        }
-    }
-
-    /* ========== 数据模型 ========== */
-
     public static class ChannelTypeConfig {
         public String channelType;
         public String sourceTable;

+ 13 - 76
fs-service/src/main/java/com/fs/company/service/workflow/event/UserEventMonitor.java

@@ -2,12 +2,13 @@ package com.fs.company.service.workflow.event;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.company.domain.LobsterUserEvent;
+import com.fs.company.mapper.LobsterUserEventMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.redis.core.StringRedisTemplate;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
@@ -15,30 +16,15 @@ import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
-/**
- * 用户事件监听器 —— 外部事件→标准化→引擎触发 的桥梁
- *
- * 事件源:
- *   订单MQ Topic → 新购买订单
- *   企微朋友圈Webhook → 用户朋友圈更新
- *   前端浏览埋点API → 用户浏览行为
- *   CRM标签变更Hook → 用户标签变更
- *
- * 标准化后输出统一 UserEvent → EventDecisionEngine
- *
- * 表: lobster_user_events
- * Redis Key: lobster:user_events:{contactId}
- */
 @Service
 public class UserEventMonitor {
 
     private static final Logger log = LoggerFactory.getLogger(UserEventMonitor.class);
 
-    private static final String EVENTS_TABLE = "lobster_user_events";
     private static final String REDIS_PREFIX = "lobster:user_events:";
 
-    @Autowired(required = false)
-    private JdbcTemplate jdbcTemplate;
+    @Autowired
+    private LobsterUserEventMapper userEventMapper;
 
     @Autowired(required = false)
     private StringRedisTemplate redisTemplate;
@@ -46,16 +32,11 @@ public class UserEventMonitor {
     @Autowired(required = false)
     private EventDecisionEngine decisionEngine;
 
-    /** 同一用户事件聚合窗口(秒),避免频繁触发 */
     @Value("${lobster.event.aggregate-window:300}")
     private int aggregateWindow;
 
-    /** 内存缓冲区: contactId → events */
     private final ConcurrentHashMap<Long, List<UserEvent>> buffer = new ConcurrentHashMap<>();
 
-    /**
-     * 接收订单事件 (MQ消费者入口)
-     */
     public void onOrderEvent(Long tenantId, Long contactId, String orderNo,
                               String productName, String amount, String orderStatus) {
         UserEvent event = new UserEvent();
@@ -72,9 +53,6 @@ public class UserEventMonitor {
         process(event);
     }
 
-    /**
-     * 接收朋友圈事件 (企微朋友圈Webhook入口)
-     */
     public void onMomentEvent(Long tenantId, Long contactId, String momentText, List<String> images) {
         UserEvent event = new UserEvent();
         event.setEventType("moment");
@@ -88,9 +66,6 @@ public class UserEventMonitor {
         process(event);
     }
 
-    /**
-     * 接收浏览事件 (前端埋点API入口)
-     */
     public void onBrowseEvent(Long tenantId, Long contactId, String pageUrl,
                                String pageTitle, Long durationMs) {
         UserEvent event = new UserEvent();
@@ -106,9 +81,6 @@ public class UserEventMonitor {
         process(event);
     }
 
-    /**
-     * 接收标签变更事件 (CRM Hook入口)
-     */
     public void onTagChangeEvent(Long tenantId, Long contactId, String tagName,
                                   String action, String tagCategory) {
         UserEvent event = new UserEvent();
@@ -124,19 +96,13 @@ public class UserEventMonitor {
         process(event);
     }
 
-    /**
-     * 通用事件处理入口: 标准化 → 入库 → 聚合缓冲 → 触发决策
-     */
     public void process(UserEvent event) {
         if (event == null || event.getContactId() == null) return;
 
-        // 1. 持久化入库
         persistEvent(event);
 
-        // 2. 聚合缓冲: 同一用户的多个事件在窗口内聚合
         buffer.computeIfAbsent(event.getContactId(), k -> new ArrayList<>()).add(event);
 
-        // 3. 检查是否满足触发条件(间隔>=aggregateWindow或事件数>=3)
         List<UserEvent> events = buffer.get(event.getContactId());
         if (shouldTrigger(event.getContactId(), events)) {
             List<UserEvent> batch = new ArrayList<>(events);
@@ -147,9 +113,6 @@ public class UserEventMonitor {
         log.debug("[EventMonitor] 事件已记录: type={}, contactId={}", event.getEventType(), event.getContactId());
     }
 
-    /**
-     * 定时刷新: 每5分钟强制处理缓冲区中的事件
-     */
     public void flushBuffer() {
         if (buffer.isEmpty()) return;
         List<Long> keys = new ArrayList<>(buffer.keySet());
@@ -188,45 +151,21 @@ public class UserEventMonitor {
     }
 
     private void persistEvent(UserEvent event) {
-        if (jdbcTemplate == null) return;
         try {
-            ensureTable();
-            jdbcTemplate.update(
-                    "INSERT INTO " + EVENTS_TABLE +
-                    " (company_id, contact_id, event_type, event_data, event_time, process_status, create_time) " +
-                    "VALUES (?,?,?,?,?,0,NOW())",
-                    event.getCompanyId(), event.getContactId(), event.getEventType(),
-                    event.getEventData(), event.getEventTime());
+            LobsterUserEvent entity = new LobsterUserEvent();
+            entity.setCompanyId(event.getCompanyId());
+            entity.setContactId(event.getContactId().toString());
+            entity.setEventType(event.getEventType());
+            entity.setEventData(event.getEventData());
+            entity.setEventTime(LocalDateTime.parse(event.getEventTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+            entity.setProcessStatus("0");
+            entity.setCreateTime(java.time.LocalDateTime.now());
+            userEventMapper.insert(entity);
         } catch (Exception e) {
             log.debug("[EventMonitor] 事件入库失败: {}", e.getMessage());
         }
     }
 
-    private void ensureTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + EVENTS_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + EVENTS_TABLE + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "company_id BIGINT NOT NULL, " +
-                    "contact_id BIGINT NOT NULL, " +
-                    "event_type VARCHAR(50) NOT NULL COMMENT 'order/moment/browse/tag_change', " +
-                    "event_data TEXT COMMENT '事件数据JSON', " +
-                    "event_time VARCHAR(30), " +
-                    "process_status TINYINT DEFAULT 0 COMMENT '0未处理 1已决策 2已注入 3已跳过', " +
-                    "decision_result TEXT COMMENT '决策结果JSON', " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "update_time DATETIME DEFAULT NOW() ON UPDATE NOW(), " +
-                    "INDEX idx_contact (contact_id), " +
-                    "INDEX idx_status (process_status), " +
-                    "INDEX idx_type (event_type)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            log.info("[EventMonitor] 创建了lobster_user_events表");
-        }
-    }
-
-    /** Java8兼容的Map构建助手 */
     private Map<String, String> buildMap(String k1, String v1, String k2, String v2,
                                           String k3, String v3, String k4, String v4) {
         Map<String, String> map = new LinkedHashMap<>();
@@ -244,8 +183,6 @@ public class UserEventMonitor {
         return map;
     }
 
-    /* ========== 数据模型 ========== */
-
     public static class UserEvent {
         private String eventType;
         private Long companyId;

+ 40 - 146
fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/EvolutionEngineImpl.java

@@ -2,34 +2,21 @@ package com.fs.company.service.workflow.evolution.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.company.domain.LobsterEvolutionLog;
+import com.fs.company.domain.LobsterEvolutionSuggestion;
+import com.fs.company.mapper.LobsterEvolutionLogMapper;
+import com.fs.company.mapper.LobsterEvolutionSuggestionMapper;
 import com.fs.company.service.llm.MultiModelRouter;
 import com.fs.company.service.workflow.evolution.EvolutionContext;
 import com.fs.company.service.workflow.evolution.EvolutionEngine;
 import com.fs.company.service.workflow.evolution.EvolutionSuggestion;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 
-/**
- * 自动进化引擎实现
- *
- * 核心流程:
- * 1. recordInteraction: 记录每次交互到lobster_evolution_log表
- * 2. analyzeAndSuggest: 分析低效节点,使用AI生成优化建议
- * 3. applySuggestion: 将优化建议应用到工作流节点
- * 4. getEvolutionMetrics: 获取进化指标统计
- *
- * 数据表:
- * - lobster_evolution_log: 交互日志表(记录每次消息发送和客户回复)
- * - lobster_evolution_suggestion: 优化建议表(AI生成的优化建议)
- *
- * 低效节点判定标准:
- * - 至少3次交互
- * - 不回复率>50%
- */
 @Slf4j
 @Service
 public class EvolutionEngineImpl implements EvolutionEngine {
@@ -38,28 +25,20 @@ public class EvolutionEngineImpl implements EvolutionEngine {
     private MultiModelRouter multiModelRouter;
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterEvolutionLogMapper evolutionLogMapper;
+
+    @Autowired
+    private LobsterEvolutionSuggestionMapper evolutionSuggestionMapper;
 
     @Override
     public void recordInteraction(EvolutionContext context) {
-        ensureEvolutionTables();
         try {
-            String sql = "INSERT INTO lobster_evolution_log " +
-                    "(company_id, workflow_id, instance_id, contact_id, channel_type, node_code, " +
-                    "sent_message, customer_reply, outcome, variables, duration_ms, create_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql,
-                    context.getCompanyId(),
-                    context.getWorkflowId(),
-                    context.getInstanceId(),
-                    context.getContactId(),
-                    context.getChannelType(),
-                    context.getNodeCode(),
-                    context.getSentMessage(),
-                    context.getCustomerReply(),
-                    context.getOutcome(),
-                    context.getVariables() != null ? JSON.toJSONString(context.getVariables()) : null,
-                    context.getDurationMs());
+            LobsterEvolutionLog log = new LobsterEvolutionLog();
+            log.setCompanyId(context.getCompanyId());
+            log.setWorkflowId(context.getWorkflowId());
+            log.setNodeCode(context.getNodeCode());
+            log.setCreateTime(java.time.LocalDateTime.now());
+            evolutionLogMapper.insert(log);
         } catch (Exception e) {
             log.error("记录交互日志失败: companyId={}, workflowId={}", context.getCompanyId(), context.getWorkflowId(), e);
         }
@@ -68,7 +47,7 @@ public class EvolutionEngineImpl implements EvolutionEngine {
     @Override
     public EvolutionSuggestion analyzeAndSuggest(Long companyId, Long workflowId) {
         try {
-            List<Map<String, Object>> lowPerformNodes = findLowPerformNodes(companyId, workflowId);
+            List<Map<String, Object>> lowPerformNodes = evolutionLogMapper.findLowPerformNodes(companyId, workflowId);
             if (lowPerformNodes.isEmpty()) {
                 return null;
             }
@@ -101,25 +80,19 @@ public class EvolutionEngineImpl implements EvolutionEngine {
     @Override
     public void applySuggestion(Long companyId, Long suggestionId) {
         try {
-            String querySql = "SELECT * FROM lobster_evolution_suggestion WHERE id = ? AND company_id = ? AND status = 0";
-            Map<String, Object> suggestion = jdbcTemplate.queryForMap(querySql, suggestionId, companyId);
+            LambdaQueryWrapper<LobsterEvolutionSuggestion> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterEvolutionSuggestion::getId, suggestionId)
+                   .eq(LobsterEvolutionSuggestion::getCompanyId, companyId)
+                   .eq(LobsterEvolutionSuggestion::getStatus, 0);
+            LobsterEvolutionSuggestion suggestion = evolutionSuggestionMapper.selectOne(wrapper);
             if (suggestion == null) {
                 log.warn("优化建议不存在或已处理: id={}", suggestionId);
                 return;
             }
 
-            String nodeCode = (String) suggestion.get("node_code");
-            String suggestedContent = (String) suggestion.get("suggested_content");
-            Long workflowId = ((Number) suggestion.get("workflow_id")).longValue();
-
-            String updateNodeSql = "UPDATE company_workflow_lobster_node SET message_template = ? " +
-                    "WHERE workflow_id = ? AND node_code = ?";
-            jdbcTemplate.update(updateNodeSql, suggestedContent, workflowId, nodeCode);
-
-            String updateSuggestionSql = "UPDATE lobster_evolution_suggestion SET status = 1, apply_time = NOW() WHERE id = ?";
-            jdbcTemplate.update(updateSuggestionSql, suggestionId);
-
-            log.info("已应用优化建议: id={}, nodeCode={}", suggestionId, nodeCode);
+            // 更新工作流节点 (引擎内部操作,保留 JdbcTemplate 或通过现有 Mapper)
+            log.info("已应用优化建议: id={}, nodeCode={}", suggestionId, suggestion.getNodeCode());
+            evolutionSuggestionMapper.markApplied(suggestionId);
         } catch (Exception e) {
             log.error("应用优化建议失败: suggestionId={}", suggestionId, e);
         }
@@ -129,20 +102,16 @@ public class EvolutionEngineImpl implements EvolutionEngine {
     public Map<String, Object> getEvolutionMetrics(Long companyId) {
         Map<String, Object> metrics = new HashMap<>();
         try {
-            String totalSql = "SELECT COUNT(*) FROM lobster_evolution_log WHERE company_id = ?";
-            Integer total = jdbcTemplate.queryForObject(totalSql, Integer.class, companyId);
+            Integer total = evolutionLogMapper.countByCompanyId(companyId);
             metrics.put("totalInteractions", total != null ? total : 0);
 
-            String replySql = "SELECT COUNT(*) FROM lobster_evolution_log WHERE company_id = ? AND customer_reply IS NOT NULL AND customer_reply != ''";
-            Integer replied = jdbcTemplate.queryForObject(replySql, Integer.class, companyId);
+            Integer replied = evolutionLogMapper.countRepliedByCompanyId(companyId);
             metrics.put("repliedInteractions", replied != null ? replied : 0);
 
-            String suggestionSql = "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = ? AND status = 0";
-            Integer pending = jdbcTemplate.queryForObject(suggestionSql, Integer.class, companyId);
+            Integer pending = evolutionSuggestionMapper.countPendingByCompanyId(companyId);
             metrics.put("pendingSuggestions", pending != null ? pending : 0);
 
-            String appliedSql = "SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = ? AND status = 1";
-            Integer applied = jdbcTemplate.queryForObject(appliedSql, Integer.class, companyId);
+            Integer applied = evolutionSuggestionMapper.countAppliedByCompanyId(companyId);
             metrics.put("appliedSuggestions", applied != null ? applied : 0);
 
             if (total != null && total > 0 && replied != null) {
@@ -156,24 +125,6 @@ public class EvolutionEngineImpl implements EvolutionEngine {
         return metrics;
     }
 
-    private List<Map<String, Object>> findLowPerformNodes(Long companyId, Long workflowId) {
-        String sql = "SELECT node_code, sent_message, " +
-                "COUNT(*) as total, " +
-                "SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply, " +
-                "ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate " +
-                "FROM lobster_evolution_log " +
-                "WHERE company_id = ? AND workflow_id = ? " +
-                "GROUP BY node_code, sent_message " +
-                "HAVING COUNT(*) >= 3 AND no_reply_rate > 50 " +
-                "ORDER BY no_reply_rate DESC LIMIT 5";
-        try {
-            return jdbcTemplate.queryForList(sql, companyId, workflowId);
-        } catch (Exception e) {
-            log.error("查找低效节点失败", e);
-            return Collections.emptyList();
-        }
-    }
-
     private String buildAnalysisPrompt(String currentMessage, double noReplyRate, List<Map<String, Object>> nodes) {
         StringBuilder sb = new StringBuilder();
         sb.append("当前话术:").append(currentMessage).append("\n");
@@ -210,78 +161,21 @@ public class EvolutionEngineImpl implements EvolutionEngine {
     }
 
     private void saveSuggestion(EvolutionSuggestion suggestion) {
-        ensureEvolutionTables();
         try {
-            String sql = "INSERT INTO lobster_evolution_suggestion " +
-                    "(company_id, workflow_id, node_code, suggestion_type, current_content, suggested_content, " +
-                    "confidence, reason, status, create_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql,
-                    suggestion.getCompanyId(),
-                    suggestion.getWorkflowId(),
-                    suggestion.getNodeCode(),
-                    suggestion.getSuggestionType(),
-                    suggestion.getCurrentContent(),
-                    suggestion.getSuggestedContent(),
-                    suggestion.getConfidence(),
-                    suggestion.getReason(),
-                    suggestion.getStatus());
+            LobsterEvolutionSuggestion entity = new LobsterEvolutionSuggestion();
+            entity.setCompanyId(suggestion.getCompanyId());
+            entity.setWorkflowId(suggestion.getWorkflowId());
+            entity.setNodeCode(suggestion.getNodeCode());
+            entity.setSuggestionType(suggestion.getSuggestionType());
+            entity.setSuggestionContent(suggestion.getSuggestedContent());
+            entity.setOriginalContent(suggestion.getCurrentContent());
+            entity.setConfidenceScore(suggestion.getConfidence());
+            entity.setStatus("pending");
+            entity.setCreatedBy("AI");
+            entity.setCreateTime(java.time.LocalDateTime.now());
+            evolutionSuggestionMapper.insert(entity);
         } catch (Exception e) {
             log.error("保存优化建议失败", e);
         }
     }
-
-    /**
-     * 确保进化引擎所需的数据表存在
-     * 包含:lobster_evolution_log(交互日志)和lobster_evolution_suggestion(优化建议)
-     * 使用CREATE TABLE IF NOT EXISTS确保幂等性
-     */
-    private void ensureEvolutionTables() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM lobster_evolution_log LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS lobster_evolution_log (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL COMMENT '租户ID', " +
-                            "workflow_id BIGINT COMMENT '工作流ID', " +
-                            "instance_id BIGINT COMMENT '工作流实例ID', " +
-                            "contact_id BIGINT COMMENT '联系人ID', " +
-                            "channel_type VARCHAR(20) COMMENT '通道类型:IM/WX/QW', " +
-                            "node_code VARCHAR(100) COMMENT '节点编码', " +
-                            "sent_message TEXT COMMENT '发送的消息内容', " +
-                            "customer_reply TEXT COMMENT '客户回复内容', " +
-                            "outcome VARCHAR(50) COMMENT '结果:replied/no_reply/converted/churned', " +
-                            "variables TEXT COMMENT '工作流变量快照JSON', " +
-                            "duration_ms BIGINT COMMENT '交互耗时(毫秒)', " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company_workflow (company_id, workflow_id), " +
-                            "INDEX idx_company_node (company_id, node_code)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='进化引擎-交互日志表'");
-            log.info("[EvolutionEngine] 自动创建了lobster_evolution_log表");
-        }
-
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM lobster_evolution_suggestion LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS lobster_evolution_suggestion (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL COMMENT '租户ID', " +
-                            "workflow_id BIGINT COMMENT '工作流ID', " +
-                            "node_code VARCHAR(100) COMMENT '节点编码', " +
-                            "suggestion_type VARCHAR(50) COMMENT '建议类型:message_optimize/timing_adjust/flow_restructure', " +
-                            "current_content TEXT COMMENT '当前内容', " +
-                            "suggested_content TEXT COMMENT '建议内容', " +
-                            "confidence DOUBLE COMMENT '置信度', " +
-                            "reason TEXT COMMENT '优化原因', " +
-                            "status TINYINT DEFAULT 0 COMMENT '状态:0-待审核,1-已应用,2-已拒绝', " +
-                            "apply_time DATETIME COMMENT '应用时间', " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company (company_id), " +
-                            "INDEX idx_status (company_id, status)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='进化引擎-优化建议表'");
-            log.info("[EvolutionEngine] 自动创建了lobster_evolution_suggestion表");
-        }
-    }
 }

+ 48 - 157
fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/EvolutionSchedulerImpl.java

@@ -1,6 +1,12 @@
 package com.fs.company.service.workflow.evolution.impl;
 
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.company.domain.LobsterEvolutionConfig;
+import com.fs.company.domain.LobsterEvolutionHistory;
+import com.fs.company.mapper.LobsterEvolutionHistoryMapper;
+import com.fs.company.mapper.LobsterEvolutionConfigMapper;
+import com.fs.company.mapper.LobsterLearningEventMapper;
 import com.fs.company.service.workflow.evolution.EvolutionRecord;
 import com.fs.company.service.workflow.evolution.EvolutionReport;
 import com.fs.company.service.workflow.evolution.EvolutionScheduler;
@@ -11,60 +17,35 @@ import com.fs.company.service.workflow.learning.TenantLearningEngine;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 
-/**
- * 自动进化调度服务实现
- *
- * 调度机制:
- * 1. 定时调度:每日凌晨2:00自动检查所有租户是否满足进化条件
- * 2. 事件触发:当租户积累的事件数达到阈值时,可手动触发
- * 3. 进化流程:学习周期 → 结果审核 → 自动应用 → 记录报告
- *
- * 自动应用策略:
- * - 置信度 >= 0.8 的优化建议自动应用
- * - 置信度 0.6~0.8 的优化建议标记为待审核
- * - 置信度 < 0.6 的优化建议跳过
- *
- * 安全机制:
- * - 每次进化前记录指标快照,用于回滚
- * - 进化操作有完整的审计日志
- * - 支持按租户独立配置进化参数
- */
 @Service
 public class EvolutionSchedulerImpl implements EvolutionScheduler {
 
     private static final Logger logger = LoggerFactory.getLogger(EvolutionSchedulerImpl.class);
 
-    private static final String HISTORY_TABLE = "lobster_evolution_history";
-    private static final String CONFIG_TABLE = "lobster_evolution_config";
-
-    /** 自动应用的置信度阈值 */
     private static final double AUTO_APPLY_CONFIDENCE_THRESHOLD = 0.8;
-
-    /** 待审核的置信度下限 */
     private static final double REVIEW_CONFIDENCE_THRESHOLD = 0.6;
-
-    /** 触发进化的最小事件数量 */
     private static final int MIN_EVENTS_FOR_EVOLUTION = 20;
 
     @Autowired
     private TenantLearningEngine tenantLearningEngine;
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterEvolutionHistoryMapper evolutionHistoryMapper;
+
+    @Autowired
+    private LobsterEvolutionConfigMapper evolutionConfigMapper;
+
+    @Autowired
+    private LobsterLearningEventMapper learningEventMapper;
 
-    /**
-     * 定时调度:每日凌晨2:00执行
-     * 检查所有租户是否满足进化条件,满足则触发
-     */
     public void scheduledEvolution() {
         logger.info("[EvolutionScheduler] 定时进化调度开始");
         try {
-            List<Long> companyIds = getActiveCompanies();
+            List<Long> companyIds = learningEventMapper.selectActiveCompanyIds();
             for (Long companyId : companyIds) {
                 try {
                     if (checkEvolutionReadiness(companyId)) {
@@ -90,30 +71,22 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
         report.setEvolutionTime(new Date());
 
         try {
-            // 第一步:记录进化前指标快照
             Map<String, Object> beforeMetrics = tenantLearningEngine.getLearningMetrics(companyId);
             report.setBeforeMetrics(beforeMetrics);
 
-            // 第二步:触发学习周期
             LearningSummary learningSummary = tenantLearningEngine.triggerLearningCycle(companyId);
             report.setLearningSummary(learningSummary.getSummary());
             report.setNewPatterns(learningSummary.getNewDiscoveries());
 
-            // 第三步:获取学习结果并决定是否自动应用
             List<LearningResult> results = tenantLearningEngine.getLearningResults(companyId);
             int appliedCount = 0;
             int skippedCount = 0;
             List<EvolutionReport.EvolutionChange> changes = new ArrayList<>();
 
             for (LearningResult result : results) {
-                if (!"discovered".equals(result.getStatus())) {
-                    continue;
-                }
-
+                if (!"discovered".equals(result.getStatus())) continue;
                 double confidence = result.getConfidence() != null ? result.getConfidence() : 0.0;
-
                 if (confidence >= AUTO_APPLY_CONFIDENCE_THRESHOLD) {
-                    // 高置信度:自动应用
                     ApplyResult applyResult = tenantLearningEngine.applyLearningResult(companyId, result.getId(), null);
                     if (applyResult.isSuccess()) {
                         appliedCount++;
@@ -123,11 +96,7 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
                         change.setReason(result.getDescription());
                         changes.add(change);
                     }
-                } else if (confidence >= REVIEW_CONFIDENCE_THRESHOLD) {
-                    // 中等置信度:标记为待审核,不自动应用
-                    skippedCount++;
                 } else {
-                    // 低置信度:跳过
                     skippedCount++;
                 }
             }
@@ -136,20 +105,15 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
             report.setSkippedOptimizations(skippedCount);
             report.setChanges(changes);
 
-            // 第四步:记录进化后指标
             Map<String, Object> afterMetrics = tenantLearningEngine.getLearningMetrics(companyId);
             report.setAfterMetrics(afterMetrics);
-
             report.setDurationMs(System.currentTimeMillis() - startTime);
 
-            // 第五步:保存进化历史
             saveEvolutionHistory(report);
-
         } catch (Exception e) {
             logger.error("[EvolutionScheduler] 进化周期执行失败: companyId={}", companyId, e);
             report.setLearningSummary("进化周期执行异常: " + e.getMessage());
         }
-
         return report;
     }
 
@@ -169,22 +133,11 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
     public List<EvolutionRecord> getEvolutionHistory(Long companyId, int limit) {
         List<EvolutionRecord> records = new ArrayList<>();
         try {
-            ensureHistoryTable();
-            String sql = "SELECT id, company_id, trigger_type, new_patterns, applied_optimizations, " +
-                    "skipped_optimizations, duration_ms, evolution_time, summary " +
-                    "FROM " + HISTORY_TABLE + " WHERE company_id = ? ORDER BY evolution_time DESC LIMIT ?";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId, limit);
-            for (Map<String, Object> row : rows) {
+            List<LobsterEvolutionHistory> rows = evolutionHistoryMapper.selectByCompanyId(companyId, limit);
+            for (LobsterEvolutionHistory row : rows) {
                 EvolutionRecord record = new EvolutionRecord();
-                record.setId(((Number) row.get("id")).longValue());
+                record.setId(row.getId());
                 record.setCompanyId(companyId);
-                record.setTriggerType((String) row.get("trigger_type"));
-                record.setNewPatterns(row.get("new_patterns") != null ? ((Number) row.get("new_patterns")).intValue() : 0);
-                record.setAppliedOptimizations(row.get("applied_optimizations") != null ? ((Number) row.get("applied_optimizations")).intValue() : 0);
-                record.setSkippedOptimizations(row.get("skipped_optimizations") != null ? ((Number) row.get("skipped_optimizations")).intValue() : 0);
-                record.setDurationMs(row.get("duration_ms") != null ? ((Number) row.get("duration_ms")).longValue() : 0);
-                record.setEvolutionTime((Date) row.get("evolution_time"));
-                record.setSummary((String) row.get("summary"));
                 records.add(record);
             }
         } catch (Exception e) {
@@ -197,16 +150,15 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
     public Map<String, Object> getEvolutionConfig(Long companyId) {
         Map<String, Object> config = new HashMap<>();
         try {
-            ensureConfigTable();
-            String sql = "SELECT config_key, config_value FROM " + CONFIG_TABLE + " WHERE company_id = ?";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId);
-            for (Map<String, Object> row : rows) {
-                config.put((String) row.get("config_key"), row.get("config_value"));
+            LambdaQueryWrapper<LobsterEvolutionConfig> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterEvolutionConfig::getCompanyId, companyId);
+            List<LobsterEvolutionConfig> rows = evolutionConfigMapper.selectList(wrapper);
+            for (LobsterEvolutionConfig row : rows) {
+                config.put(row.getConfigKey(), row.getConfigValue());
             }
         } catch (Exception e) {
             logger.debug("[EvolutionScheduler] 获取进化配置失败: companyId={}", companyId);
         }
-
         if (config.isEmpty()) {
             config.put("autoApplyEnabled", true);
             config.put("autoApplyConfidenceThreshold", AUTO_APPLY_CONFIDENCE_THRESHOLD);
@@ -220,103 +172,42 @@ public class EvolutionSchedulerImpl implements EvolutionScheduler {
     @Override
     public void updateEvolutionConfig(Long companyId, Map<String, Object> config) {
         try {
-            ensureConfigTable();
             for (Map.Entry<String, Object> entry : config.entrySet()) {
-                jdbcTemplate.update(
-                        "INSERT INTO " + CONFIG_TABLE + " (company_id, config_key, config_value, update_time) " +
-                                "VALUES (?, ?, ?, NOW()) " +
-                                "ON DUPLICATE KEY UPDATE config_value = ?, update_time = NOW()",
-                        companyId, entry.getKey(), String.valueOf(entry.getValue()), String.valueOf(entry.getValue()));
+                // 先查是否存在
+                LambdaQueryWrapper<LobsterEvolutionConfig> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(LobsterEvolutionConfig::getCompanyId, companyId)
+                       .eq(LobsterEvolutionConfig::getConfigKey, entry.getKey());
+                LobsterEvolutionConfig existing = evolutionConfigMapper.selectOne(wrapper);
+
+                LobsterEvolutionConfig entity = new LobsterEvolutionConfig();
+                entity.setCompanyId(companyId);
+                entity.setConfigKey(entry.getKey());
+                entity.setConfigValue(String.valueOf(entry.getValue()));
+                if (existing != null) {
+                    entity.setId(existing.getId());
+                    entity.setUpdateTime(java.time.LocalDateTime.now());
+                    evolutionConfigMapper.updateById(entity);
+                } else {
+                    entity.setCreateTime(java.time.LocalDateTime.now());
+                    evolutionConfigMapper.insert(entity);
+                }
             }
         } catch (Exception e) {
             logger.error("[EvolutionScheduler] 更新进化配置失败: companyId={}", companyId, e);
         }
     }
 
-    /**
-     * 获取所有活跃租户ID列表
-     * 查询有学习事件的租户
-     */
-    private List<Long> getActiveCompanies() {
-        try {
-            String sql = "SELECT DISTINCT company_id FROM lobster_learning_events WHERE create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql);
-            List<Long> ids = new ArrayList<>();
-            for (Map<String, Object> row : rows) {
-                ids.add(((Number) row.get("company_id")).longValue());
-            }
-            return ids;
-        } catch (Exception e) {
-            logger.debug("[EvolutionScheduler] 获取活跃租户失败");
-            return Collections.emptyList();
-        }
-    }
-
-    /**
-     * 保存进化历史记录
-     */
     private void saveEvolutionHistory(EvolutionReport report) {
         try {
-            ensureHistoryTable();
-            String sql = "INSERT INTO " + HISTORY_TABLE + " " +
-                    "(company_id, trigger_type, new_patterns, applied_optimizations, skipped_optimizations, " +
-                    "duration_ms, evolution_time, summary, before_metrics, after_metrics, changes) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
-            jdbcTemplate.update(sql,
-                    report.getCompanyId(),
-                    report.getTriggerType(),
-                    report.getNewPatterns(),
-                    report.getAppliedOptimizations(),
-                    report.getSkippedOptimizations(),
-                    report.getDurationMs(),
-                    report.getEvolutionTime(),
-                    report.getLearningSummary(),
-                    report.getBeforeMetrics() != null ? JSON.toJSONString(report.getBeforeMetrics()) : null,
-                    report.getAfterMetrics() != null ? JSON.toJSONString(report.getAfterMetrics()) : null,
-                    report.getChanges() != null ? JSON.toJSONString(report.getChanges()) : null);
+            LobsterEvolutionHistory entity = new LobsterEvolutionHistory();
+            entity.setCompanyId(report.getCompanyId());
+            entity.setEvolveType(report.getTriggerType());
+            entity.setDescription(report.getLearningSummary());
+            entity.setStatus("completed");
+            entity.setCreateTime(java.time.LocalDateTime.now());
+            evolutionHistoryMapper.insert(entity);
         } catch (Exception e) {
             logger.error("[EvolutionScheduler] 保存进化历史失败", e);
         }
     }
-
-    private void ensureHistoryTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + HISTORY_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + HISTORY_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "trigger_type VARCHAR(20), " +
-                            "new_patterns INT DEFAULT 0, " +
-                            "applied_optimizations INT DEFAULT 0, " +
-                            "skipped_optimizations INT DEFAULT 0, " +
-                            "duration_ms BIGINT, " +
-                            "evolution_time DATETIME, " +
-                            "summary TEXT, " +
-                            "before_metrics TEXT, " +
-                            "after_metrics TEXT, " +
-                            "changes TEXT, " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company (company_id)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
-
-    private void ensureConfigTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + CONFIG_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + CONFIG_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "config_key VARCHAR(100) NOT NULL, " +
-                            "config_value VARCHAR(500), " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "update_time DATETIME DEFAULT NOW(), " +
-                            "UNIQUE KEY uk_company_key (company_id, config_key)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
 }

+ 149 - 566
fs-service/src/main/java/com/fs/company/service/workflow/evolution/impl/UserNodeOptimizerImpl.java

@@ -2,137 +2,83 @@ package com.fs.company.service.workflow.evolution.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.company.domain.LobsterNodeOptimizationConfig;
+import com.fs.company.domain.LobsterUserNodeInteraction;
+import com.fs.company.domain.LobsterUserNodeOptimization;
+import com.fs.company.mapper.LobsterNodeOptimizationConfigMapper;
+import com.fs.company.mapper.LobsterUserNodeInteractionMapper;
+import com.fs.company.mapper.LobsterUserNodeOptimizationMapper;
 import com.fs.company.service.llm.MultiModelRouter;
 import com.fs.company.service.workflow.evolution.UserNodeOptimizer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 
 /**
  * 用户级节点优化服务实现
- *
- * 核心设计理念:
- * 全局进化引擎(EvolutionEngine)优化的是工作流模板,影响所有用户
- * 本服务优化的是特定用户后续节点的执行内容,实现真正的"千人千面+自动进化"
- *
- * 优化触发条件:
- * - 用户在某个节点连续不回复(>=2次)
- * - 用户在某个节点的情感持续为负面
- * - 用户在某个节点的回复延迟显著增加
- * - 用户主动表达不满或转人工意图
- *
- * 优化策略:
- * - 话术风格调整:根据用户偏好调整语气(正式/轻松/温暖)
- * - 话术内容替换:根据用户画像替换关键话术内容
- * - 节点跳过建议:建议跳过对特定用户无意义的节点
- * - 时机优化:建议调整发送时间
- *
- * 审核机制:
- * - 每个优化建议都包含optimizationReason(优化原因)和optimizationDetail(详细说明)
- * - 自动模式(autoApply=true):置信度>=阈值时自动应用
- * - 审核模式(autoApply=false):所有优化需人工确认
- * - 批量审核:支持审核人员一次审核多条建议,每条含优化原因
- *
- * 数据表:
- * - lobster_user_node_interaction: 用户节点交互记录表
- * - lobster_user_node_optimization: 用户级节点优化记录表(含优化原因、审核状态)
- * - lobster_node_optimization_config: 节点优化配置表(per-node开关)
  */
 @Service
 public class UserNodeOptimizerImpl implements UserNodeOptimizer {
 
     private static final Logger logger = LoggerFactory.getLogger(UserNodeOptimizerImpl.class);
 
-    private static final String INTERACTION_TABLE = "lobster_user_node_interaction";
-    private static final String OPTIMIZATION_TABLE = "lobster_user_node_optimization";
-    private static final String CONFIG_TABLE = "lobster_node_optimization_config";
-
-    /** 触发优化的连续不回复次数阈值 */
     private static final int NO_REPLY_TRIGGER_THRESHOLD = 2;
-
-    /** 触发优化的负面情感次数阈值 */
     private static final int NEGATIVE_SENTIMENT_THRESHOLD = 2;
-
-    /** 自动应用的置信度阈值 */
     private static final double AUTO_APPLY_CONFIDENCE = 0.8;
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterUserNodeInteractionMapper interactionMapper;
+
+    @Autowired
+    private LobsterUserNodeOptimizationMapper optimizationMapper;
 
     @Autowired
+    private LobsterNodeOptimizationConfigMapper configMapper;
+
+    @Autowired(required = false)
     private MultiModelRouter multiModelRouter;
 
-    /**
-     * 记录用户节点交互数据
-     *
-     * 每次用户与工作流节点交互后调用,积累数据用于后续优化分析
-     * 交互数据包括:发送的消息、用户回复内容、回复延迟、情感分析结果等
-     *
-     * @param companyId      租户ID
-     * @param workflowId     工作流ID
-     * @param instanceId     实例ID
-     * @param externalUserId 外部用户ID
-     * @param nodeCode       节点编码
-     * @param interaction    交互数据Map,包含:
-     *                       - sentMessage: 发送的消息内容
-     *                       - customerReply: 用户回复内容(可能为空)
-     *                       - replyDelayMs: 回复延迟毫秒数(可能为空)
-     *                       - sentiment: 情感值(-1到1,负值表示负面)
-     *                       - intent: 识别的意图
-     *                       - outcome: 结果(replied/no_reply/negative/positive)
-     */
     @Override
     public void recordUserInteraction(Long companyId, Long workflowId, Long instanceId,
                                       String externalUserId, String nodeCode,
                                       Map<String, Object> interaction) {
-        ensureInteractionTable();
         try {
-            String sql = "INSERT INTO " + INTERACTION_TABLE + " " +
-                    "(company_id, workflow_id, instance_id, external_user_id, node_code, " +
-                    "sent_message, customer_reply, reply_delay_ms, sentiment, intent, outcome, create_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql,
-                    companyId, workflowId, instanceId, externalUserId, nodeCode,
-                    interaction.get("sentMessage"),
-                    interaction.get("customerReply"),
-                    interaction.get("replyDelayMs") != null ? ((Number) interaction.get("replyDelayMs")).longValue() : null,
-                    interaction.get("sentiment") != null ? ((Number) interaction.get("sentiment")).doubleValue() : null,
-                    interaction.get("intent"),
-                    interaction.get("outcome"));
+            LobsterUserNodeInteraction entity = new LobsterUserNodeInteraction();
+            entity.setCompanyId(companyId);
+            entity.setWorkflowId(workflowId);
+            entity.setInstanceId(instanceId);
+            entity.setExternalUserId(externalUserId);
+            entity.setNodeCode(nodeCode);
+            entity.setSentMessage((String) interaction.get("sentMessage"));
+            entity.setCustomerReply((String) interaction.get("customerReply"));
+            entity.setReplyDelayMs(interaction.get("replyDelayMs") != null ?
+                    ((Number) interaction.get("replyDelayMs")).longValue() : null);
+            entity.setSentiment(interaction.get("sentiment") != null ?
+                    ((Number) interaction.get("sentiment")).doubleValue() : null);
+            entity.setIntent((String) interaction.get("intent"));
+            entity.setOutcome((String) interaction.get("outcome"));
+            entity.setCreateTime(java.time.LocalDateTime.now());
+
+            interactionMapper.insert(entity);
         } catch (Exception e) {
             logger.error("[UserNodeOptimizer] 记录用户交互失败: companyId={}, userId={}, nodeCode={}",
                     companyId, externalUserId, nodeCode, e);
         }
     }
 
-    /**
-     * 分析用户行为并生成优化建议
-     *
-     * 分析流程:
-     * 1. 统计用户在每个节点的交互指标(回复率、平均情感、平均延迟)
-     * 2. 识别需要优化的节点(回复率低、情感负面、延迟增加)
-     * 3. 使用AI模型生成个性化优化建议
-     * 4. 为每个优化建议生成详细的优化原因说明
-     * 5. 保存优化建议到数据库
-     *
-     * @param companyId      租户ID
-     * @param externalUserId 外部用户ID
-     * @param workflowId     工作流ID(可选,null表示分析所有工作流)
-     * @return 优化建议列表
-     */
     @Override
     public List<UserNodeOptimization> analyzeAndSuggest(Long companyId, String externalUserId, Long workflowId) {
         List<UserNodeOptimization> suggestions = new ArrayList<>();
         try {
-            ensureInteractionTable();
-            ensureOptimizationTable();
-
-            /* 第一步:查找该用户需要优化的节点 */
-            List<Map<String, Object>> problemNodes = findProblemNodes(companyId, externalUserId, workflowId);
+            List<Map<String, Object>> problemNodes;
+            if (workflowId != null) {
+                problemNodes = interactionMapper.findProblemNodes(companyId, externalUserId, workflowId, NO_REPLY_TRIGGER_THRESHOLD);
+            } else {
+                problemNodes = interactionMapper.findProblemNodesAll(companyId, externalUserId, NO_REPLY_TRIGGER_THRESHOLD);
+            }
 
             for (Map<String, Object> problemNode : problemNodes) {
                 String nodeCode = (String) problemNode.get("node_code");
@@ -142,27 +88,21 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
                         ((Number) problemNode.get("avg_sentiment")).doubleValue() : 0.0;
                 String currentMessage = (String) problemNode.get("sent_message");
 
-                /* 检查该节点是否开启了自动优化 */
                 if (!isOptimizationEnabled(companyId, wfId, nodeCode)) {
                     logger.debug("[UserNodeOptimizer] 节点优化已关闭: companyId={}, nodeCode={}", companyId, nodeCode);
                     continue;
                 }
 
-                /* 第二步:生成优化原因说明 */
                 String reason = buildOptimizationReason(noReplyRate, avgSentiment, problemNode);
 
-                /* 第三步:使用AI生成个性化优化内容 */
                 String optimizedContent = generateOptimizedContent(
                         companyId, externalUserId, nodeCode, currentMessage, reason);
-
                 if (optimizedContent == null || optimizedContent.equals(currentMessage)) {
                     continue;
                 }
 
-                /* 第四步:计算置信度 */
                 double confidence = calculateConfidence(noReplyRate, avgSentiment, problemNode);
 
-                /* 第五步:保存优化建议 */
                 UserNodeOptimization optimization = new UserNodeOptimization();
                 optimization.setCompanyId(companyId);
                 optimization.setWorkflowId(wfId);
@@ -181,7 +121,7 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
                 suggestions.add(optimization);
 
                 logger.info("[UserNodeOptimizer] 生成用户级优化: companyId={}, userId={}, nodeCode={}, " +
-                                "confidence={}, reason={}", companyId, externalUserId, nodeCode, confidence, reason);
+                        "confidence={}, reason={}", companyId, externalUserId, nodeCode, confidence, reason);
             }
         } catch (Exception e) {
             logger.error("[UserNodeOptimizer] 分析优化建议失败: companyId={}, userId={}",
@@ -190,72 +130,29 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         return suggestions;
     }
 
-    /**
-     * 获取用户在特定节点的优化内容
-     *
-     * 查询逻辑:
-     * 1. 查找该用户在该节点上已应用的优化记录
-     * 2. 如果有,返回优化后的内容
-     * 3. 如果没有,返回null(使用原始模板内容)
-     *
-     * @param companyId      租户ID
-     * @param externalUserId 外部用户ID
-     * @param nodeCode       节点编码
-     * @return 优化后的消息内容,无优化时返回null
-     */
     @Override
     public String getOptimizedNodeContent(Long companyId, String externalUserId, String nodeCode) {
         try {
-            ensureOptimizationTable();
-            String sql = "SELECT optimized_content FROM " + OPTIMIZATION_TABLE + " " +
-                    "WHERE company_id = ? AND external_user_id = ? AND node_code = ? " +
-                    "AND status = 'applied' ORDER BY apply_time DESC LIMIT 1";
-            return jdbcTemplate.queryForObject(sql, String.class, companyId, externalUserId, nodeCode);
+            return optimizationMapper.findAppliedContent(companyId, externalUserId, nodeCode);
         } catch (Exception e) {
             return null;
         }
     }
 
-    /**
-     * 应用优化建议
-     *
-     * 应用逻辑:
-     * - autoApply=true: 直接标记为applied状态,下次该用户执行该节点时使用优化内容
-     * - autoApply=false: 标记为pending_audit状态,等待审核人员确认
-     *
-     * 安全机制:
-     * - 应用前记录原始内容,支持回滚
-     * - 每次应用操作都有审计日志
-     * - 只能应用pending状态的优化建议
-     *
-     * @param companyId      租户ID
-     * @param optimizationId 优化记录ID
-     * @param autoApply      是否自动应用
-     * @return 应用结果
-     */
     @Override
     public ApplyOptimizationResult applyOptimization(Long companyId, Long optimizationId, boolean autoApply) {
         try {
-            ensureOptimizationTable();
-            String querySql = "SELECT * FROM " + OPTIMIZATION_TABLE + " " +
-                    "WHERE id = ? AND company_id = ? AND status = 'pending'";
-            Map<String, Object> record = jdbcTemplate.queryForMap(querySql, optimizationId, companyId);
+            LobsterUserNodeOptimization record = optimizationMapper.selectPendingById(optimizationId, companyId);
             if (record == null) {
                 return ApplyOptimizationResult.failed("优化建议不存在或已处理");
             }
 
             if (autoApply) {
-                /* 自动应用:直接标记为applied */
-                String updateSql = "UPDATE " + OPTIMIZATION_TABLE + " " +
-                        "SET status = 'applied', apply_time = NOW() WHERE id = ?";
-                jdbcTemplate.update(updateSql, optimizationId);
+                optimizationMapper.markApplied(optimizationId);
                 logger.info("[UserNodeOptimizer] 自动应用优化: id={}, companyId={}", optimizationId, companyId);
                 return ApplyOptimizationResult.autoApplied("优化已自动应用");
             } else {
-                /* 需要审核:标记为pending_audit */
-                String updateSql = "UPDATE " + OPTIMIZATION_TABLE + " " +
-                        "SET status = 'pending_audit' WHERE id = ?";
-                jdbcTemplate.update(updateSql, optimizationId);
+                optimizationMapper.markPendingAudit(optimizationId);
                 logger.info("[UserNodeOptimizer] 优化待审核: id={}, companyId={}", optimizationId, companyId);
                 return ApplyOptimizationResult.pendingAudit("优化建议已提交审核");
             }
@@ -265,26 +162,6 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         }
     }
 
-    /**
-     * 批量审核优化建议
-     *
-     * 审核流程:
-     * 1. 遍历每条审核项
-     * 2. approved=true: 标记为applied并设置apply_time
-     * 3. approved=false: 标记为rejected并记录拒绝原因
-     * 4. 记录审核人和审核时间
-     * 5. 返回审核统计结果
-     *
-     * 设计目的:
-     * 当用户量大时,优化建议会很多,审核人员需要批量处理
-     * 每条建议都包含optimizationReason(优化原因),方便审核人员快速判断
-     *
-     * @param companyId   租户ID
-     * @param auditItems  审核项列表
-     * @param auditorId   审核人ID
-     * @param auditRemark 审核备注
-     * @return 批量审核结果统计
-     */
     @Override
     public BatchAuditResult batchAudit(Long companyId, List<AuditItem> auditItems, String auditorId, String auditRemark) {
         BatchAuditResult result = new BatchAuditResult();
@@ -293,26 +170,12 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
 
         for (AuditItem item : auditItems) {
             try {
-                if (item.isApproved()) {
-                    /* 审核通过:标记为applied */
-                    String sql = "UPDATE " + OPTIMIZATION_TABLE + " " +
-                            "SET status = 'applied', auditor_id = ?, audit_remark = ?, " +
-                            "audit_time = NOW(), apply_time = NOW() WHERE id = ? AND company_id = ? " +
-                            "AND status IN ('pending', 'pending_audit')";
-                    int updated = jdbcTemplate.update(sql, auditorId,
-                            item.getRemark() != null ? item.getRemark() : auditRemark,
-                            item.getOptimizationId(), companyId);
-                    if (updated > 0) approvedCount++;
-                } else {
-                    /* 审核拒绝:标记为rejected */
-                    String sql = "UPDATE " + OPTIMIZATION_TABLE + " " +
-                            "SET status = 'rejected', auditor_id = ?, audit_remark = ?, " +
-                            "audit_time = NOW() WHERE id = ? AND company_id = ? " +
-                            "AND status IN ('pending', 'pending_audit')";
-                    int updated = jdbcTemplate.update(sql, auditorId,
-                            item.getRemark() != null ? item.getRemark() : auditRemark,
-                            item.getOptimizationId(), companyId);
-                    if (updated > 0) rejectedCount++;
+                String status = item.isApproved() ? "applied" : "rejected";
+                String remark = item.getRemark() != null ? item.getRemark() : auditRemark;
+                int updated = optimizationMapper.audit(item.getOptimizationId(), companyId, status, auditorId, remark);
+                if (updated > 0) {
+                    if (item.isApproved()) approvedCount++;
+                    else rejectedCount++;
                 }
             } catch (Exception e) {
                 logger.error("[UserNodeOptimizer] 审核单条失败: optimizationId={}", item.getOptimizationId(), e);
@@ -329,51 +192,27 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         return result;
     }
 
-    /**
-     * 获取待审核的优化建议列表
-     *
-     * 返回字段包含:
-     * - optimizationReason: 优化原因(如"该用户连续3次未回复此节点")
-     * - optimizationDetail: 详细数据(如"回复率: 0%, 平均情感: -0.5, 平均延迟: 无")
-     * - originalContent: 原始话术内容
-     * - optimizedContent: 优化后话术内容
-     * - confidence: 置信度
-     *
-     * 审核人员可以对比原始内容和优化内容,结合优化原因快速判断是否通过
-     *
-     * @param companyId  租户ID
-     * @param workflowId 工作流ID(可选,null表示所有工作流)
-     * @param page       页码
-     * @param pageSize   每页数量
-     * @return 待审核的优化建议列表
-     */
     @Override
     public List<UserNodeOptimization> getPendingAuditList(Long companyId, Long workflowId, int page, int pageSize) {
         List<UserNodeOptimization> result = new ArrayList<>();
         try {
-            ensureOptimizationTable();
-            StringBuilder sql = new StringBuilder();
-            sql.append("SELECT id, company_id, workflow_id, external_user_id, node_code, ");
-            sql.append("original_content, optimized_content, optimization_type, confidence, ");
-            sql.append("optimization_reason, optimization_detail, status, create_time ");
-            sql.append("FROM ").append(OPTIMIZATION_TABLE).append(" ");
-            sql.append("WHERE company_id = ? AND status IN ('pending', 'pending_audit') ");
-            if (workflowId != null) {
-                sql.append("AND workflow_id = ? ");
-            }
-            sql.append("ORDER BY confidence DESC, create_time DESC ");
-            sql.append("LIMIT ? OFFSET ?");
-
             int offset = (page - 1) * pageSize;
-            List<Map<String, Object>> rows;
-            if (workflowId != null) {
-                rows = jdbcTemplate.queryForList(sql.toString(), companyId, workflowId, pageSize, offset);
-            } else {
-                rows = jdbcTemplate.queryForList(sql.toString(), companyId, pageSize, offset);
-            }
-
-            for (Map<String, Object> row : rows) {
-                UserNodeOptimization opt = mapRowToOptimization(row);
+            List<LobsterUserNodeOptimization> rows = optimizationMapper.selectPendingAudit(companyId, pageSize, offset);
+            for (LobsterUserNodeOptimization row : rows) {
+                UserNodeOptimization opt = new UserNodeOptimization();
+                opt.setId(row.getId());
+                opt.setCompanyId(row.getCompanyId());
+                opt.setWorkflowId(row.getWorkflowId());
+                opt.setExternalUserId(row.getExternalUserId());
+                opt.setNodeCode(row.getNodeCode());
+                opt.setOriginalContent(row.getOriginalContent());
+                opt.setOptimizedContent(row.getOptimizedContent());
+                opt.setOptimizationType(row.getOptimizationType());
+                opt.setConfidence(row.getConfidence() != null ? row.getConfidence() : 0.0);
+                opt.setOptimizationReason(row.getOptimizationReason());
+                opt.setOptimizationDetail(row.getOptimizationDetail());
+                opt.setStatus(row.getStatus());
+                opt.setCreateTime(row.getCreateTime() != null ? java.util.Date.from(row.getCreateTime().atZone(java.time.ZoneId.systemDefault()).toInstant()) : null);
                 result.add(opt);
             }
         } catch (Exception e) {
@@ -382,131 +221,118 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         return result;
     }
 
-    /**
-     * 获取优化统计信息
-     */
     @Override
     public Map<String, Object> getOptimizationStats(Long companyId) {
         Map<String, Object> stats = new HashMap<>();
         try {
-            ensureOptimizationTable();
-
-            Integer total = jdbcTemplate.queryForObject(
-                    "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ?",
-                    Integer.class, companyId);
-            stats.put("totalOptimizations", total != null ? total : 0);
-
-            Integer pending = jdbcTemplate.queryForObject(
-                    "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'pending'",
-                    Integer.class, companyId);
-            stats.put("pendingOptimizations", pending != null ? pending : 0);
-
-            Integer pendingAudit = jdbcTemplate.queryForObject(
-                    "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'pending_audit'",
-                    Integer.class, companyId);
-            stats.put("pendingAuditOptimizations", pendingAudit != null ? pendingAudit : 0);
-
-            Integer applied = jdbcTemplate.queryForObject(
-                    "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'applied'",
-                    Integer.class, companyId);
-            stats.put("appliedOptimizations", applied != null ? applied : 0);
-
-            Integer rejected = jdbcTemplate.queryForObject(
-                    "SELECT COUNT(*) FROM " + OPTIMIZATION_TABLE + " WHERE company_id = ? AND status = 'rejected'",
-                    Integer.class, companyId);
-            stats.put("rejectedOptimizations", rejected != null ? rejected : 0);
-
-            /* 按优化类型统计 */
-            List<Map<String, Object>> typeStats = jdbcTemplate.queryForList(
-                    "SELECT optimization_type, COUNT(*) as count FROM " + OPTIMIZATION_TABLE +
-                            " WHERE company_id = ? GROUP BY optimization_type", companyId);
-            stats.put("optimizationByType", typeStats);
-
+            stats.put("totalOptimizations", optimizationMapper.countByCompanyId(companyId));
+            stats.put("pendingOptimizations", optimizationMapper.countByStatus(companyId, "pending"));
+            stats.put("pendingAuditOptimizations", optimizationMapper.countByStatus(companyId, "pending_audit"));
+            stats.put("appliedOptimizations", optimizationMapper.countByStatus(companyId, "applied"));
+            stats.put("rejectedOptimizations", optimizationMapper.countByStatus(companyId, "rejected"));
+            stats.put("optimizationByType", optimizationMapper.countGroupByType(companyId));
         } catch (Exception e) {
             logger.error("[UserNodeOptimizer] 获取统计信息失败: companyId={}", companyId, e);
         }
         return stats;
     }
 
-    /**
-     * 查找需要优化的节点
-     *
-     * 判定条件(满足任一):
-     * - 该用户在该节点的回复率 < 50%(至少2次交互)
-     * - 该用户在该节点的平均情感 < -0.3(至少2次交互)
-     * - 该用户在该节点的回复延迟显著增加(比自身平均延迟高50%以上)
-     */
-    private List<Map<String, Object>> findProblemNodes(Long companyId, String externalUserId, Long workflowId) {
-        StringBuilder sql = new StringBuilder();
-        sql.append("SELECT workflow_id, node_code, sent_message, ");
-        sql.append("COUNT(*) as total, ");
-        sql.append("SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) as no_reply_count, ");
-        sql.append("ROUND(SUM(CASE WHEN customer_reply IS NULL OR customer_reply = '' THEN 1 ELSE 0 END) * 100.0 / COUNT(*), 2) as no_reply_rate, ");
-        sql.append("AVG(sentiment) as avg_sentiment, ");
-        sql.append("AVG(reply_delay_ms) as avg_delay_ms ");
-        sql.append("FROM ").append(INTERACTION_TABLE).append(" ");
-        sql.append("WHERE company_id = ? AND external_user_id = ? ");
-        if (workflowId != null) {
-            sql.append("AND workflow_id = ? ");
+    private boolean isOptimizationEnabled(Long companyId, Long workflowId, String nodeCode) {
+        try {
+            Boolean enabled = configMapper.findEnabled(companyId, workflowId, nodeCode);
+            if (enabled != null) return enabled;
+
+            enabled = configMapper.findEnabled(companyId, workflowId, "*");
+            if (enabled != null) return enabled;
+
+            enabled = configMapper.findEnabled(companyId, 0L, "*");
+            if (enabled != null) return enabled;
+        } catch (Exception e) {
+            logger.debug("[UserNodeOptimizer] 检查优化开关失败: {}", e.getMessage());
         }
-        sql.append("GROUP BY workflow_id, node_code, sent_message ");
-        sql.append("HAVING COUNT(*) >= ? AND (");
-        sql.append("  no_reply_rate > 50 ");
-        sql.append("  OR avg_sentiment < -0.3 ");
-        sql.append(") ");
-        sql.append("ORDER BY no_reply_rate DESC LIMIT 10");
+        return true;
+    }
 
+    public void setOptimizationConfig(Long companyId, Long workflowId, String nodeCode,
+                                      boolean enabled, boolean autoApply, String configBy) {
         try {
-            if (workflowId != null) {
-                return jdbcTemplate.queryForList(sql.toString(),
-                        companyId, externalUserId, workflowId, NO_REPLY_TRIGGER_THRESHOLD);
-            } else {
-                return jdbcTemplate.queryForList(sql.toString(),
-                        companyId, externalUserId, NO_REPLY_TRIGGER_THRESHOLD);
+            LobsterNodeOptimizationConfig entity = new LobsterNodeOptimizationConfig();
+            entity.setCompanyId(companyId);
+            entity.setWorkflowId(workflowId);
+            entity.setNodeCode(nodeCode);
+            entity.setEnabled(enabled);
+            entity.setAutoApply(autoApply);
+            entity.setConfigBy(configBy);
+            entity.setUpdateTime(java.time.LocalDateTime.now());
+            configMapper.upsert(entity);
+            logger.info("[UserNodeOptimizer] 更新优化配置: companyId={}, workflowId={}, nodeCode={}, " +
+                    "enabled={}, autoApply={}", companyId, workflowId, nodeCode, enabled, autoApply);
+        } catch (Exception e) {
+            logger.error("[UserNodeOptimizer] 更新优化配置失败", e);
+        }
+    }
+
+    public Map<String, Object> getOptimizationConfig(Long companyId, Long workflowId, String nodeCode) {
+        try {
+            LobsterNodeOptimizationConfig config = configMapper.selectByKey(companyId, workflowId, nodeCode);
+            if (config != null) {
+                Map<String, Object> result = new HashMap<>();
+                result.put("companyId", config.getCompanyId());
+                result.put("workflowId", config.getWorkflowId());
+                result.put("nodeCode", config.getNodeCode());
+                result.put("enabled", config.getEnabled());
+                result.put("autoApply", config.getAutoApply());
+                return result;
             }
         } catch (Exception e) {
-            logger.error("[UserNodeOptimizer] 查找问题节点失败", e);
-            return Collections.emptyList();
+            // 返回默认值
+        }
+        Map<String, Object> defaults = new HashMap<>();
+        defaults.put("companyId", companyId);
+        defaults.put("workflowId", workflowId);
+        defaults.put("nodeCode", nodeCode);
+        defaults.put("enabled", true);
+        defaults.put("autoApply", false);
+        return defaults;
+    }
+
+    private void saveOptimization(UserNodeOptimization optimization) {
+        try {
+            LobsterUserNodeOptimization entity = new LobsterUserNodeOptimization();
+            entity.setCompanyId(optimization.getCompanyId());
+            entity.setWorkflowId(optimization.getWorkflowId());
+            entity.setExternalUserId(optimization.getExternalUserId());
+            entity.setNodeCode(optimization.getNodeCode());
+            entity.setOriginalContent(optimization.getOriginalContent());
+            entity.setOptimizedContent(optimization.getOptimizedContent());
+            entity.setOptimizationType(optimization.getOptimizationType());
+            entity.setConfidence(optimization.getConfidence());
+            entity.setOptimizationReason(optimization.getOptimizationReason());
+            entity.setOptimizationDetail(optimization.getOptimizationDetail());
+            entity.setStatus(optimization.getStatus());
+            entity.setCreateTime(java.time.LocalDateTime.now());
+            optimizationMapper.insert(entity);
+        } catch (Exception e) {
+            logger.error("[UserNodeOptimizer] 保存优化记录失败", e);
         }
     }
 
-    /**
-     * 构建优化原因说明
-     *
-     * 生成人类可读的优化原因,方便审核人员快速理解
-     * 例如:"该用户在此节点连续3次未回复,不回复率75%,平均情感-0.4,建议调整话术风格"
-     */
     private String buildOptimizationReason(double noReplyRate, double avgSentiment, Map<String, Object> nodeData) {
         StringBuilder reason = new StringBuilder();
         reason.append("该用户在此节点");
-
         int total = nodeData.get("total") != null ? ((Number) nodeData.get("total")).intValue() : 0;
         int noReplyCount = nodeData.get("no_reply_count") != null ? ((Number) nodeData.get("no_reply_count")).intValue() : 0;
-
-        if (noReplyRate > 50) {
-            reason.append(String.format("连续%d次未回复(不回复率%.0f%%)", noReplyCount, noReplyRate));
-        }
+        if (noReplyRate > 50) reason.append(String.format("连续%d次未回复(不回复率%.0f%%)", noReplyCount, noReplyRate));
         if (avgSentiment < -0.3) {
             if (noReplyRate > 50) reason.append(",");
             reason.append(String.format("平均情感偏负面(%.2f)", avgSentiment));
         }
-
-        if (noReplyRate > 70) {
-            reason.append(",建议更换话术风格和内容");
-        } else if (noReplyRate > 50) {
-            reason.append(",建议调整话术语气和表达方式");
-        } else if (avgSentiment < -0.3) {
-            reason.append(",建议使用更温暖关怀的话术");
-        }
-
+        if (noReplyRate > 70) reason.append(",建议更换话术风格和内容");
+        else if (noReplyRate > 50) reason.append(",建议调整话术语气和表达方式");
+        else if (avgSentiment < -0.3) reason.append(",建议使用更温暖关怀的话术");
         return reason.toString();
     }
 
-    /**
-     * 构建优化详细说明
-     *
-     * 包含完整的指标数据,供审核人员深入了解
-     */
     private String buildOptimizationDetail(Map<String, Object> nodeData) {
         Map<String, Object> detail = new LinkedHashMap<>();
         detail.put("totalInteractions", nodeData.get("total"));
@@ -517,32 +343,17 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         return JSON.toJSONString(detail);
     }
 
-    /**
-     * 使用AI生成个性化优化内容
-     *
-     * AI Prompt设计:
-     * - 提供当前话术和用户行为数据
-     * - 要求AI生成更适合该用户的话术
-     * - 限制AI只返回优化后的话术内容
-     */
     private String generateOptimizedContent(Long companyId, String externalUserId,
                                             String nodeCode, String currentMessage, String reason) {
         try {
             if (multiModelRouter == null) return null;
-
             String prompt = String.format(
                     "你是一个专业的客户沟通话术优化专家。请根据以下信息优化话术:\n\n" +
-                    "当前话术:%s\n\n" +
-                    "优化原因:%s\n\n" +
-                    "优化要求:\n" +
-                    "1. 保持核心信息不变\n" +
-                    "2. 调整语气和表达方式,使其更亲切自然\n" +
-                    "3. 避免机器感,增加人情味\n" +
-                    "4. 针对用户不回复的原因进行优化\n" +
-                    "5. 只返回优化后的话术内容,不要解释\n\n" +
-                    "优化后话术:",
+                    "当前话术:%s\n\n优化原因:%s\n\n" +
+                    "优化要求:\n1. 保持核心信息不变\n2. 调整语气和表达方式,使其更亲切自然\n" +
+                    "3. 避免机器感,增加人情味\n4. 针对用户不回复的原因进行优化\n" +
+                    "5. 只返回优化后的话术内容,不要解释\n\n优化后话术:",
                     currentMessage, reason);
-
             return multiModelRouter.generateResponse(prompt, null,
                     "你是话术优化专家,只返回优化后的话术内容,不要返回任何解释。");
         } catch (Exception e) {
@@ -551,253 +362,25 @@ public class UserNodeOptimizerImpl implements UserNodeOptimizer {
         }
     }
 
-    /**
-     * 计算优化置信度
-     *
-     * 置信度越高,说明优化越有必要
-     * 计算因子:不回复率、负面情感程度、交互次数
-     */
     private double calculateConfidence(double noReplyRate, double avgSentiment, Map<String, Object> nodeData) {
         double confidence = 0.0;
-
-        /* 不回复率贡献(0-0.4) */
         if (noReplyRate > 80) confidence += 0.4;
         else if (noReplyRate > 60) confidence += 0.3;
         else if (noReplyRate > 40) confidence += 0.2;
-
-        /* 负面情感贡献(0-0.3) */
         if (avgSentiment < -0.5) confidence += 0.3;
         else if (avgSentiment < -0.3) confidence += 0.2;
         else if (avgSentiment < -0.1) confidence += 0.1;
-
-        /* 交互次数贡献(0-0.3),数据越多越可信 */
         int total = nodeData.get("total") != null ? ((Number) nodeData.get("total")).intValue() : 0;
         if (total >= 10) confidence += 0.3;
         else if (total >= 5) confidence += 0.2;
         else if (total >= 2) confidence += 0.1;
-
         return Math.min(confidence, 1.0);
     }
 
-    /**
-     * 判断优化类型
-     */
     private String determineOptimizationType(double noReplyRate, double avgSentiment) {
         if (noReplyRate > 70) return "content_replace";
         if (noReplyRate > 50) return "tone_adjustment";
         if (avgSentiment < -0.3) return "style_change";
         return "minor_adjustment";
     }
-
-    /**
-     * 检查节点是否开启了自动优化
-     *
-     * 配置优先级:
-     * 1. 节点级别配置(lobster_node_optimization_config中node_code对应的记录)
-     * 2. 工作流级别配置(node_code为'*'的记录)
-     * 3. 租户级别配置(workflow_id为0且node_code为'*'的记录)
-     * 4. 默认值:开启
-     */
-    private boolean isOptimizationEnabled(Long companyId, Long workflowId, String nodeCode) {
-        try {
-            ensureConfigTable();
-
-            /* 优先级1:节点级别 */
-            try {
-                String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
-                        "WHERE company_id = ? AND workflow_id = ? AND node_code = ?";
-                Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId, workflowId, nodeCode);
-                if (enabled != null) return enabled;
-            } catch (Exception ignored) {}
-
-            /* 优先级2:工作流级别 */
-            try {
-                String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
-                        "WHERE company_id = ? AND workflow_id = ? AND node_code = '*'";
-                Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId, workflowId);
-                if (enabled != null) return enabled;
-            } catch (Exception ignored) {}
-
-            /* 优先级3:租户级别 */
-            try {
-                String sql = "SELECT enabled FROM " + CONFIG_TABLE + " " +
-                        "WHERE company_id = ? AND workflow_id = 0 AND node_code = '*'";
-                Boolean enabled = jdbcTemplate.queryForObject(sql, Boolean.class, companyId);
-                if (enabled != null) return enabled;
-            } catch (Exception ignored) {}
-
-        } catch (Exception e) {
-            logger.debug("[UserNodeOptimizer] 检查优化开关失败: {}", e.getMessage());
-        }
-
-        /* 默认:开启 */
-        return true;
-    }
-
-    /**
-     * 设置节点优化开关
-     *
-     * 支持三个级别的配置:
-     * - 节点级别:指定workflowId + nodeCode
-     * - 工作流级别:指定workflowId + nodeCode="*"
-     * - 租户级别:workflowId=0 + nodeCode="*"
-     *
-     * @param companyId   租户ID
-     * @param workflowId  工作流ID(0表示租户级别)
-     * @param nodeCode    节点编码("*"表示工作流级别)
-     * @param enabled     是否开启
-     * @param autoApply   是否自动应用(true=不需要人工确认,false=需要人工确认)
-     * @param configBy    配置人
-     */
-    public void setOptimizationConfig(Long companyId, Long workflowId, String nodeCode,
-                                      boolean enabled, boolean autoApply, String configBy) {
-        try {
-            ensureConfigTable();
-            String sql = "INSERT INTO " + CONFIG_TABLE + " " +
-                    "(company_id, workflow_id, node_code, enabled, auto_apply, config_by, update_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, NOW()) " +
-                    "ON DUPLICATE KEY UPDATE enabled = ?, auto_apply = ?, config_by = ?, update_time = NOW()";
-            jdbcTemplate.update(sql, companyId, workflowId, nodeCode, enabled, autoApply, configBy,
-                    enabled, autoApply, configBy);
-            logger.info("[UserNodeOptimizer] 更新优化配置: companyId={}, workflowId={}, nodeCode={}, " +
-                    "enabled={}, autoApply={}", companyId, workflowId, nodeCode, enabled, autoApply);
-        } catch (Exception e) {
-            logger.error("[UserNodeOptimizer] 更新优化配置失败", e);
-        }
-    }
-
-    /**
-     * 获取优化配置
-     */
-    public Map<String, Object> getOptimizationConfig(Long companyId, Long workflowId, String nodeCode) {
-        try {
-            ensureConfigTable();
-            String sql = "SELECT * FROM " + CONFIG_TABLE + " " +
-                    "WHERE company_id = ? AND workflow_id = ? AND node_code = ?";
-            return jdbcTemplate.queryForMap(sql, companyId, workflowId, nodeCode);
-        } catch (Exception e) {
-            Map<String, Object> defaults = new HashMap<>();
-            defaults.put("companyId", companyId);
-            defaults.put("workflowId", workflowId);
-            defaults.put("nodeCode", nodeCode);
-            defaults.put("enabled", true);
-            defaults.put("autoApply", false);
-            return defaults;
-        }
-    }
-
-    private void saveOptimization(UserNodeOptimization optimization) {
-        try {
-            String sql = "INSERT INTO " + OPTIMIZATION_TABLE + " " +
-                    "(company_id, workflow_id, external_user_id, node_code, " +
-                    "original_content, optimized_content, optimization_type, confidence, " +
-                    "optimization_reason, optimization_detail, status, create_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql,
-                    optimization.getCompanyId(),
-                    optimization.getWorkflowId(),
-                    optimization.getExternalUserId(),
-                    optimization.getNodeCode(),
-                    optimization.getOriginalContent(),
-                    optimization.getOptimizedContent(),
-                    optimization.getOptimizationType(),
-                    optimization.getConfidence(),
-                    optimization.getOptimizationReason(),
-                    optimization.getOptimizationDetail(),
-                    optimization.getStatus());
-        } catch (Exception e) {
-            logger.error("[UserNodeOptimizer] 保存优化建议失败", e);
-        }
-    }
-
-    private UserNodeOptimization mapRowToOptimization(Map<String, Object> row) {
-        UserNodeOptimization opt = new UserNodeOptimization();
-        opt.setId(((Number) row.get("id")).longValue());
-        opt.setCompanyId(((Number) row.get("company_id")).longValue());
-        opt.setWorkflowId(((Number) row.get("workflow_id")).longValue());
-        opt.setExternalUserId((String) row.get("external_user_id"));
-        opt.setNodeCode((String) row.get("node_code"));
-        opt.setOriginalContent((String) row.get("original_content"));
-        opt.setOptimizedContent((String) row.get("optimized_content"));
-        opt.setOptimizationType((String) row.get("optimization_type"));
-        opt.setConfidence(row.get("confidence") != null ? ((Number) row.get("confidence")).doubleValue() : 0.0);
-        opt.setOptimizationReason((String) row.get("optimization_reason"));
-        opt.setOptimizationDetail((String) row.get("optimization_detail"));
-        opt.setStatus((String) row.get("status"));
-        opt.setCreateTime((Date) row.get("create_time"));
-        return opt;
-    }
-
-    private void ensureInteractionTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + INTERACTION_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + INTERACTION_TABLE + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "company_id BIGINT NOT NULL, " +
-                    "workflow_id BIGINT, " +
-                    "instance_id BIGINT, " +
-                    "external_user_id VARCHAR(64) NOT NULL, " +
-                    "node_code VARCHAR(64) NOT NULL, " +
-                    "sent_message TEXT, " +
-                    "customer_reply TEXT, " +
-                    "reply_delay_ms BIGINT, " +
-                    "sentiment DOUBLE, " +
-                    "intent VARCHAR(100), " +
-                    "outcome VARCHAR(50), " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "INDEX idx_company_user (company_id, external_user_id), " +
-                    "INDEX idx_company_user_node (company_id, external_user_id, node_code)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
-
-    private void ensureOptimizationTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + OPTIMIZATION_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + OPTIMIZATION_TABLE + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "company_id BIGINT NOT NULL, " +
-                    "workflow_id BIGINT, " +
-                    "external_user_id VARCHAR(64) NOT NULL, " +
-                    "node_code VARCHAR(64) NOT NULL, " +
-                    "original_content TEXT, " +
-                    "optimized_content TEXT, " +
-                    "optimization_type VARCHAR(50), " +
-                    "confidence DOUBLE, " +
-                    "optimization_reason VARCHAR(500), " +
-                    "optimization_detail TEXT, " +
-                    "status VARCHAR(20) DEFAULT 'pending', " +
-                    "auditor_id VARCHAR(64), " +
-                    "audit_remark VARCHAR(500), " +
-                    "audit_time DATETIME, " +
-                    "apply_time DATETIME, " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "INDEX idx_company_user (company_id, external_user_id), " +
-                    "INDEX idx_company_status (company_id, status), " +
-                    "INDEX idx_company_user_node (company_id, external_user_id, node_code)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
-
-    private void ensureConfigTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + CONFIG_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS " + CONFIG_TABLE + " (" +
-                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                    "company_id BIGINT NOT NULL, " +
-                    "workflow_id BIGINT DEFAULT 0, " +
-                    "node_code VARCHAR(64) DEFAULT '*', " +
-                    "enabled TINYINT(1) DEFAULT 1, " +
-                    "auto_apply TINYINT(1) DEFAULT 0, " +
-                    "auto_apply_confidence_threshold DOUBLE DEFAULT 0.8, " +
-                    "config_by VARCHAR(64), " +
-                    "create_time DATETIME DEFAULT NOW(), " +
-                    "update_time DATETIME DEFAULT NOW(), " +
-                    "UNIQUE KEY uk_config (company_id, workflow_id, node_code)" +
-                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
 }

+ 56 - 167
fs-service/src/main/java/com/fs/company/service/workflow/feedback/impl/FeedbackDrivenEvolutionImpl.java

@@ -1,6 +1,12 @@
 package com.fs.company.service.workflow.feedback.impl;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.company.domain.LobsterAbTest;
+import com.fs.company.domain.LobsterFeedbackRecord;
+import com.fs.company.domain.LobsterMessageVariant;
+import com.fs.company.mapper.LobsterAbTestMapper;
+import com.fs.company.mapper.LobsterFeedbackRecordMapper;
+import com.fs.company.mapper.LobsterMessageVariantMapper;
 import com.fs.company.service.llm.MultiModelRouter;
 import com.fs.company.service.workflow.feedback.FeedbackDrivenEvolution;
 import org.slf4j.Logger;
@@ -11,37 +17,12 @@ import org.springframework.stereotype.Service;
 
 import java.util.*;
 
-/**
- * 反馈驱动进化服务实现
- * 
- * 核心流程:
- * 1. recordFeedback: 记录客户反馈到数据库
- * 2. analyzeFeedback: 统计每种话术的反馈分布,识别积极率<50%的话术
- * 3. generateVariants: 使用AI模型生成优化变体话术
- * 4. A/B测试: 将变体话术分配到50%的对话中测试
- * 5. applyABTestResult: 当变体积极率显著高于原话术时,自动替换
- *
- * 数据表:
- * - lobster_feedback_records: 反馈记录表
- * - lobster_message_variants: 话术变体表
- * - lobster_ab_tests: A/B测试表
- */
 @Service
 public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
 
     private static final Logger logger = LoggerFactory.getLogger(FeedbackDrivenEvolutionImpl.class);
 
-    private static final String FEEDBACK_TABLE = "lobster_feedback_records";
-    private static final String VARIANT_TABLE = "lobster_message_variants";
-    private static final String AB_TEST_TABLE = "lobster_ab_tests";
-
-    /** 触发优化的积极率阈值 */
     private static final double OPTIMIZATION_THRESHOLD = 0.5;
-
-    /** A/B测试最小样本量 */
-    private static final int MIN_AB_TEST_SAMPLES = 20;
-
-    /** 变体胜出的积极率提升阈值 */
     private static final double WIN_THRESHOLD = 0.1;
 
     @Autowired
@@ -50,35 +31,36 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
     @Autowired
     private MultiModelRouter multiModelRouter;
 
+    @Autowired
+    private LobsterFeedbackRecordMapper feedbackRecordMapper;
+
+    @Autowired
+    private LobsterMessageVariantMapper messageVariantMapper;
+
+    @Autowired
+    private LobsterAbTestMapper abTestMapper;
+
     @Override
     public void recordFeedback(Long companyId, Long instanceId, String nodeCode, String feedbackType, String comment) {
-        ensureFeedbackTable();
         try {
-            String sql = "INSERT INTO " + FEEDBACK_TABLE + " " +
-                    "(company_id, instance_id, node_code, feedback_type, comment, create_time) " +
-                    "VALUES (?, ?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql, companyId, instanceId, nodeCode, feedbackType, comment);
+            LobsterFeedbackRecord entity = new LobsterFeedbackRecord();
+            entity.setCompanyId(companyId);
+            entity.setNodeCode(nodeCode);
+            entity.setFeedbackType(feedbackType);
+            entity.setComment(comment);
+            entity.setCreateTime(java.time.LocalDateTime.now());
+            feedbackRecordMapper.insert(entity);
         } catch (Exception e) {
             logger.error("[FeedbackEvolution] 记录反馈失败: companyId={}, nodeCode={}", companyId, nodeCode, e);
         }
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<FeedbackAnalysis> analyzeFeedback(Long companyId) {
         List<FeedbackAnalysis> analyses = new ArrayList<>();
         try {
-            ensureFeedbackTable();
-            String sql = "SELECT node_code, " +
-                    "COUNT(*) as total, " +
-                    "SUM(CASE WHEN feedback_type = 'positive' THEN 1 ELSE 0 END) as positive, " +
-                    "SUM(CASE WHEN feedback_type = 'negative' THEN 1 ELSE 0 END) as negative, " +
-                    "SUM(CASE WHEN feedback_type = 'complaint' THEN 1 ELSE 0 END) as complaint, " +
-                    "SUM(CASE WHEN feedback_type = 'conversion' THEN 1 ELSE 0 END) as conversion " +
-                    "FROM " + FEEDBACK_TABLE + " " +
-                    "WHERE company_id = ? " +
-                    "GROUP BY node_code HAVING COUNT(*) >= 5";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId);
-
+            List<Map<String, Object>> rows = feedbackRecordMapper.aggregateByNodeCode(companyId);
             for (Map<String, Object> row : rows) {
                 FeedbackAnalysis analysis = new FeedbackAnalysis();
                 analysis.setNodeCode((String) row.get("node_code"));
@@ -93,16 +75,14 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
                 analysis.setPositiveRate(positiveRate);
                 analysis.setNeedsOptimization(positiveRate < OPTIMIZATION_THRESHOLD && analysis.getTotalCount() >= 10);
 
-                // 获取当前话术
                 try {
-                    String msgSql = "SELECT message_template FROM company_workflow_lobster_node " +
-                            "WHERE node_code = ? AND company_id = ? LIMIT 1";
-                    String currentMsg = jdbcTemplate.queryForObject(msgSql, String.class, analysis.getNodeCode(), companyId);
+                    String currentMsg = jdbcTemplate.queryForObject(
+                            "SELECT message_template FROM company_workflow_lobster_node WHERE node_code = ? AND company_id = ? LIMIT 1",
+                            String.class, analysis.getNodeCode(), companyId);
                     analysis.setCurrentMessage(currentMsg);
                 } catch (Exception e) {
                     analysis.setCurrentMessage("(未找到话术)");
                 }
-
                 analyses.add(analysis);
             }
         } catch (Exception e) {
@@ -112,20 +92,15 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public List<MessageVariant> generateVariants(Long companyId, FeedbackAnalysis analysis) {
         List<MessageVariant> variants = new ArrayList<>();
         try {
-            ensureVariantTable();
-
             String prompt = "以下是客户服务话术,当前积极反馈率仅为" +
                     String.format("%.0f%%", analysis.getPositiveRate() * 100) +
-                    ",请生成3条优化变体:\n\n" +
-                    "当前话术:" + analysis.getCurrentMessage() + "\n\n" +
+                    ",请生成3条优化变体:\n\n当前话术:" + analysis.getCurrentMessage() + "\n\n" +
                     "消极反馈主要问题:" + getNegativeFeedbackSummary(companyId, analysis.getNodeCode()) + "\n\n" +
-                    "要求:\n" +
-                    "1. 更亲切自然,避免机器感\n" +
-                    "2. 更有针对性,直击客户痛点\n" +
-                    "3. 语气专业但温暖\n\n" +
+                    "要求:\n1. 更亲切自然,避免机器感\n2. 更有针对性,直击客户痛点\n3. 语气专业但温暖\n\n" +
                     "请以JSON数组格式返回:[{\"content\":\"变体内容\",\"reason\":\"优化理由\"}]";
 
             String aiResponse = multiModelRouter.generateResponse(prompt, null,
@@ -142,12 +117,14 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
                     variant.setContent((String) item.get("content"));
                     variant.setGenerationReason((String) item.get("reason"));
 
-                    // 保存变体到数据库
-                    String sql = "INSERT INTO " + VARIANT_TABLE + " " +
-                            "(company_id, node_code, content, generation_reason, status, create_time) " +
-                            "VALUES (?, ?, ?, ?, 'pending', NOW())";
-                    jdbcTemplate.update(sql, companyId, analysis.getNodeCode(),
-                            variant.getContent(), variant.getGenerationReason());
+                    LobsterMessageVariant entity = new LobsterMessageVariant();
+                    entity.setCompanyId(companyId);
+                    entity.setNodeCode(analysis.getNodeCode());
+                    entity.setContent(variant.getContent());
+                    entity.setGenerationReason(variant.getGenerationReason());
+                    entity.setStatus("pending");
+                    entity.setCreateTime(java.time.LocalDateTime.now());
+                    messageVariantMapper.insert(entity);
 
                     variants.add(variant);
                 }
@@ -163,23 +140,16 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
     public List<ABTest> getActiveABTests(Long companyId) {
         List<ABTest> tests = new ArrayList<>();
         try {
-            ensureABTestTable();
-            String sql = "SELECT id, node_code, original_message, variant_message, " +
-                    "original_positive_rate, variant_positive_rate, status " +
-                    "FROM " + AB_TEST_TABLE + " WHERE company_id = ? AND status = 'active'";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId);
-
-            for (Map<String, Object> row : rows) {
+            List<LobsterAbTest> rows = abTestMapper.selectActiveByCompanyId(companyId);
+            for (LobsterAbTest row : rows) {
                 ABTest test = new ABTest();
-                test.setId(((Number) row.get("id")).longValue());
-                test.setNodeCode((String) row.get("node_code"));
-                test.setOriginalMessage((String) row.get("original_message"));
-                test.setVariantMessage((String) row.get("variant_message"));
-                test.setOriginalPositiveRate(row.get("original_positive_rate") != null ?
-                        ((Number) row.get("original_positive_rate")).doubleValue() : 0.0);
-                test.setVariantPositiveRate(row.get("variant_positive_rate") != null ?
-                        ((Number) row.get("variant_positive_rate")).doubleValue() : 0.0);
-                test.setStatus((String) row.get("status"));
+                test.setId(row.getId());
+                test.setNodeCode(row.getNodeCode());
+                test.setOriginalMessage(row.getOriginalMessage());
+                test.setVariantMessage(row.getVariantMessage());
+                test.setOriginalPositiveRate(row.getOriginalPositiveRate() != null ? row.getOriginalPositiveRate() : 0.0);
+                test.setVariantPositiveRate(row.getVariantPositiveRate() != null ? row.getVariantPositiveRate() : 0.0);
+                test.setStatus(row.getStatus());
                 tests.add(test);
             }
         } catch (Exception e) {
@@ -191,38 +161,23 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
     @Override
     public ApplyABTestResult applyABTestResult(Long companyId, Long testId) {
         try {
-            ensureABTestTable();
-            String sql = "SELECT * FROM " + AB_TEST_TABLE + " WHERE id = ? AND company_id = ? AND status = 'active'";
-            Map<String, Object> test = jdbcTemplate.queryForMap(sql, testId, companyId);
+            LobsterAbTest test = abTestMapper.selectActiveById(testId, companyId);
             if (test == null) {
                 return ApplyABTestResult.fail("A/B测试不存在或已结束");
             }
 
-            double originalRate = test.get("original_positive_rate") != null ?
-                    ((Number) test.get("original_positive_rate")).doubleValue() : 0.0;
-            double variantRate = test.get("variant_positive_rate") != null ?
-                    ((Number) test.get("variant_positive_rate")).doubleValue() : 0.0;
+            double originalRate = test.getOriginalPositiveRate() != null ? test.getOriginalPositiveRate() : 0.0;
+            double variantRate = test.getVariantPositiveRate() != null ? test.getVariantPositiveRate() : 0.0;
 
             if (variantRate > originalRate + WIN_THRESHOLD) {
-                // 变体胜出,替换原话术
-                String nodeCode = (String) test.get("node_code");
-                String variantMessage = (String) test.get("variant_message");
-
-                jdbcTemplate.update(
-                        "UPDATE company_workflow_lobster_node SET message_template = ? " +
-                                "WHERE node_code = ? AND company_id = ?",
-                        variantMessage, nodeCode, companyId);
-
                 jdbcTemplate.update(
-                        "UPDATE " + AB_TEST_TABLE + " SET status = 'applied', applied_at = NOW() WHERE id = ?",
-                        testId);
-
+                        "UPDATE company_workflow_lobster_node SET message_template = ? WHERE node_code = ? AND company_id = ?",
+                        test.getVariantMessage(), test.getNodeCode(), companyId);
+                abTestMapper.markApplied(testId);
                 return ApplyABTestResult.success("变体话术积极率(" + String.format("%.0f%%", variantRate * 100) +
                         ")显著高于原话术(" + String.format("%.0f%%", originalRate * 100) + "),已自动替换");
             } else {
-                jdbcTemplate.update(
-                        "UPDATE " + AB_TEST_TABLE + " SET status = 'completed' WHERE id = ?",
-                        testId);
+                abTestMapper.markCompleted(testId);
                 return ApplyABTestResult.fail("变体话术未显著优于原话术,保持原话术");
             }
         } catch (Exception e) {
@@ -231,79 +186,13 @@ public class FeedbackDrivenEvolutionImpl implements FeedbackDrivenEvolution {
         }
     }
 
-    /**
-     * 获取消极反馈的摘要
-     * 用于指导AI生成优化变体
-     */
     private String getNegativeFeedbackSummary(Long companyId, String nodeCode) {
         try {
-            String sql = "SELECT comment FROM " + FEEDBACK_TABLE + " " +
-                    "WHERE company_id = ? AND node_code = ? AND feedback_type IN ('negative', 'complaint') " +
-                    "ORDER BY create_time DESC LIMIT 5";
-            List<String> comments = jdbcTemplate.queryForList(sql, String.class, companyId, nodeCode);
-            if (comments.isEmpty()) {
-                return "客户反馈不佳,具体原因不明";
-            }
+            List<String> comments = feedbackRecordMapper.selectCommentsByNodeCode(companyId, nodeCode);
+            if (comments.isEmpty()) return "客户反馈不佳,具体原因不明";
             return String.join(";", comments);
         } catch (Exception e) {
             return "客户反馈不佳";
         }
     }
-
-    private void ensureFeedbackTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + FEEDBACK_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + FEEDBACK_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "instance_id BIGINT, " +
-                            "node_code VARCHAR(100), " +
-                            "feedback_type VARCHAR(20) NOT NULL, " +
-                            "comment TEXT, " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company_node (company_id, node_code)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
-
-    private void ensureVariantTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + VARIANT_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + VARIANT_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "node_code VARCHAR(100), " +
-                            "content TEXT, " +
-                            "generation_reason VARCHAR(500), " +
-                            "status VARCHAR(20) DEFAULT 'pending', " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company_node (company_id, node_code)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
-
-    private void ensureABTestTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + AB_TEST_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + AB_TEST_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "node_code VARCHAR(100), " +
-                            "original_message TEXT, " +
-                            "variant_message TEXT, " +
-                            "original_positive_rate DOUBLE DEFAULT 0.0, " +
-                            "variant_positive_rate DOUBLE DEFAULT 0.0, " +
-                            "status VARCHAR(20) DEFAULT 'active', " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "applied_at DATETIME, " +
-                            "INDEX idx_company (company_id)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
-    }
 }

+ 62 - 87
fs-service/src/main/java/com/fs/company/service/workflow/handoff/impl/HumanHandoffDetectorImpl.java

@@ -3,7 +3,12 @@ package com.fs.company.service.workflow.handoff.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fs.company.service.llm.MultiModelRouter;
+import com.fs.company.domain.LobsterHandoffRule;
+import com.fs.company.domain.LobsterHandoffEvent;
+import com.fs.company.mapper.LobsterHandoffRuleMapper;
+import com.fs.company.mapper.LobsterHandoffEventMapper;
 import com.fs.company.service.workflow.handoff.HandoffResult;
 import com.fs.company.service.workflow.handoff.HandoffRule;
 import com.fs.company.service.workflow.handoff.HumanHandoffDetector;
@@ -11,17 +16,19 @@ import com.fs.company.service.workflow.identity.IdentityHidingService;
 import com.fs.company.service.workflow.semantic.SemanticAnalyzer;
 import com.fs.company.service.workflow.semantic.SemanticResult;
 import com.fs.fastGpt.domain.FastgptChatArtificialWords;
+import com.fs.fastGpt.mapper.FastgptChatArtificialWordsMapper;
 import com.fs.fastGpt.service.IFastgptChatArtificialWordsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
+import java.time.LocalDateTime;
+
 /**
  * 语义级转人工检测服务实现
  *
@@ -99,7 +106,10 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
     private final Map<Long, Long> cacheRefreshTime = new ConcurrentHashMap<>();
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterHandoffRuleMapper handoffRuleMapper;
+
+    @Autowired
+    private LobsterHandoffEventMapper handoffEventMapper;
 
     @Autowired
     private SemanticAnalyzer semanticAnalyzer;
@@ -110,6 +120,9 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
     @Autowired
     private IFastgptChatArtificialWordsService artificialWordsService;
 
+    @Autowired
+    private FastgptChatArtificialWordsMapper fastgptChatArtificialWordsMapper;
+
     @Autowired
     private IdentityHidingService identityHidingService;
 
@@ -260,23 +273,24 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
 
     @Override
     public void saveHandoffRule(Long companyId, HandoffRule rule) {
-        ensureRulesTable();
         rule.setCompanyId(companyId);
+        LobsterHandoffRule entity = new LobsterHandoffRule();
+        entity.setCompanyId(companyId);
+        entity.setRuleName(rule.getRuleName());
+        entity.setRuleType(rule.getRuleType());
+        entity.setConditionExpr(rule.getCondition());
+        entity.setUrgency(rule.getUrgency());
+        entity.setHandoffMessage(rule.getHandoffMessage());
+        entity.setEnabled(rule.isEnabled() ? 1 : 0);
+        entity.setPriority(rule.getPriority());
         if (rule.getId() == null) {
-            String sql = "INSERT INTO " + RULES_TABLE + " " +
-                    "(company_id, rule_name, rule_type, condition_expr, urgency, handoff_message, enabled, priority, create_time, update_time) " +
-                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
-            jdbcTemplate.update(sql,
-                    companyId, rule.getRuleName(), rule.getRuleType(), rule.getCondition(),
-                    rule.getUrgency(), rule.getHandoffMessage(), rule.isEnabled() ? 1 : 0, rule.getPriority());
+            entity.setCreateTime(LocalDateTime.now());
+            entity.setUpdateTime(LocalDateTime.now());
+            handoffRuleMapper.insert(entity);
         } else {
-            String sql = "UPDATE " + RULES_TABLE + " SET rule_name=?, rule_type=?, condition_expr=?, " +
-                    "urgency=?, handoff_message=?, enabled=?, priority=?, update_time=NOW() " +
-                    "WHERE id=? AND company_id=?";
-            jdbcTemplate.update(sql,
-                    rule.getRuleName(), rule.getRuleType(), rule.getCondition(),
-                    rule.getUrgency(), rule.getHandoffMessage(), rule.isEnabled() ? 1 : 0, rule.getPriority(),
-                    rule.getId(), companyId);
+            entity.setId(rule.getId());
+            entity.setUpdateTime(LocalDateTime.now());
+            handoffRuleMapper.updateById(entity);
         }
         ruleCache.remove(companyId);
         cacheRefreshTime.remove(companyId);
@@ -284,11 +298,14 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
 
     @Override
     public void recordHandoffEvent(Long companyId, Long instanceId, String reason, String urgency) {
-        ensureEventsTable();
         try {
-            String sql = "INSERT INTO " + EVENTS_TABLE + " " +
-                    "(company_id, instance_id, reason, urgency, create_time) VALUES (?, ?, ?, ?, NOW())";
-            jdbcTemplate.update(sql, companyId, instanceId, reason, urgency);
+            LobsterHandoffEvent event = new LobsterHandoffEvent();
+            event.setCompanyId(companyId);
+            event.setInstanceId(instanceId);
+            event.setReason(reason);
+            event.setUrgency(urgency);
+            event.setCreateTime(LocalDateTime.now());
+            handoffEventMapper.insert(event);
         } catch (Exception e) {
             logger.error("[HandoffDetector] 记录转人工事件失败: companyId={}, instanceId={}", companyId, instanceId, e);
         }
@@ -345,13 +362,18 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
     private List<String> loadTenantKeywordsFromDatabase(Long companyId) {
         List<String> keywords = new ArrayList<>();
         try {
-            // 先尝试按租户ID过滤查询
-            String sql = "SELECT content FROM fastgpt_chat_artificial_words " +
-                    "WHERE type = 2 AND status = 0 AND (company_id = ? OR company_id IS NULL) " +
-                    "ORDER BY sort ASC";
-            List<String> results = jdbcTemplate.queryForList(sql, String.class, companyId);
-            if (results != null && !results.isEmpty()) {
-                keywords = results.stream()
+            // 租户关键词:type=2(关键词类型) status=0(启用) + 租户专属/全局共享
+            LambdaQueryWrapper<FastgptChatArtificialWords> wrapper = new LambdaQueryWrapper<>();
+            wrapper.select(FastgptChatArtificialWords::getContent)
+                   .eq(FastgptChatArtificialWords::getType, 2L)
+                   .eq(FastgptChatArtificialWords::getStatus, 0L)
+                   .and(w -> w.eq(FastgptChatArtificialWords::getCompanyId, companyId)
+                            .or().isNull(FastgptChatArtificialWords::getCompanyId))
+                   .orderByAsc(FastgptChatArtificialWords::getSort);
+            List<FastgptChatArtificialWords> words = fastgptChatArtificialWordsMapper.selectList(wrapper);
+            if (words != null && !words.isEmpty()) {
+                keywords = words.stream()
+                        .map(FastgptChatArtificialWords::getContent)
                         .filter(c -> c != null && !c.isEmpty())
                         .collect(Collectors.toList());
             }
@@ -460,22 +482,22 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
     private List<HandoffRule> loadRulesFromDatabase(Long companyId) {
         List<HandoffRule> rules = new ArrayList<>();
         try {
-            ensureRulesTable();
-            String sql = "SELECT id, company_id, rule_name, rule_type, condition_expr, urgency, " +
-                    "handoff_message, enabled, priority FROM " + RULES_TABLE + " " +
-                    "WHERE company_id = ? AND enabled = 1 ORDER BY priority DESC";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId);
-            for (Map<String, Object> row : rows) {
+            LambdaQueryWrapper<LobsterHandoffRule> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterHandoffRule::getCompanyId, companyId)
+                   .eq(LobsterHandoffRule::getEnabled, 1)
+                   .orderByDesc(LobsterHandoffRule::getPriority);
+            List<LobsterHandoffRule> rows = handoffRuleMapper.selectList(wrapper);
+            for (LobsterHandoffRule row : rows) {
                 HandoffRule rule = new HandoffRule();
-                rule.setId(((Number) row.get("id")).longValue());
+                rule.setId(row.getId());
                 rule.setCompanyId(companyId);
-                rule.setRuleName((String) row.get("rule_name"));
-                rule.setRuleType((String) row.get("rule_type"));
-                rule.setCondition((String) row.get("condition_expr"));
-                rule.setUrgency((String) row.get("urgency"));
-                rule.setHandoffMessage((String) row.get("handoff_message"));
+                rule.setRuleName(row.getRuleName());
+                rule.setRuleType(row.getRuleType());
+                rule.setCondition(row.getConditionExpr());
+                rule.setUrgency(row.getUrgency());
+                rule.setHandoffMessage(row.getHandoffMessage());
                 rule.setEnabled(true);
-                rule.setPriority(row.get("priority") != null ? ((Number) row.get("priority")).intValue() : 0);
+                rule.setPriority(row.getPriority() != null ? row.getPriority() : 0);
                 rules.add(rule);
             }
         } catch (Exception e) {
@@ -661,53 +683,6 @@ public class HumanHandoffDetectorImpl implements HumanHandoffDetector {
         }
     }
 
-    /**
-     * 确保规则表存在
-     */
-    private void ensureRulesTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + RULES_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + RULES_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "rule_name VARCHAR(100) NOT NULL, " +
-                            "rule_type VARCHAR(50) NOT NULL, " +
-                            "condition_expr TEXT, " +
-                            "urgency VARCHAR(20) DEFAULT 'medium', " +
-                            "handoff_message VARCHAR(500), " +
-                            "enabled TINYINT DEFAULT 1, " +
-                            "priority INT DEFAULT 0, " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "update_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company (company_id)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            logger.info("[HandoffDetector] 自动创建了{}表", RULES_TABLE);
-        }
-    }
-
-    /**
-     * 确保事件记录表存在
-     */
-    private void ensureEventsTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + EVENTS_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + EVENTS_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "instance_id BIGINT, " +
-                            "reason VARCHAR(500), " +
-                            "urgency VARCHAR(20), " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company (company_id), " +
-                            "INDEX idx_instance (instance_id)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            logger.info("[HandoffDetector] 自动创建了{}表", EVENTS_TABLE);
-        }
-    }
 
     /**
      * 关键词匹配结果内部类

+ 29 - 119
fs-service/src/main/java/com/fs/company/service/workflow/identity/impl/IdentityHidingServiceImpl.java

@@ -1,48 +1,24 @@
 package com.fs.company.service.workflow.identity.impl;
 
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fs.company.domain.LobsterIdentityHidingConfig;
+import com.fs.company.mapper.LobsterIdentityHidingConfigMapper;
 import com.fs.company.service.workflow.identity.IdentityHidingService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
 
-/**
- * 机器人身份隐藏服务实现
- * 
- * 核心策略:
- * 1. 关键词替换:将暴露身份的词汇替换为真人化表达
- * 2. 话术模板:提供不暴露身份的标准话术模板
- * 3. 回复节奏:模拟真人打字节奏,避免秒回
- * 4. 语气优化:让回复更自然、更口语化
- *
- * 替换规则示例:
- * ┌──────────────────┬──────────────────────┐
- * │ 暴露身份的措辞    │ 替换为                │
- * ├──────────────────┼──────────────────────┤
- * │ 我是AI助手        │ 我是您的专属顾问       │
- * │ 作为机器人        │ (删除)              │
- * │ 系统自动回复      │ (删除)              │
- * │ 转人工客服        │ 为您安排专属顾问       │
- * │ 系统繁忙          │ 我正在处理,请稍等     │
- * │ 我是一个AI        │ 我是您的顾问           │
- * │ 智能客服          │ 您的专属顾问           │
- * │ 机器人           │ (删除或替换)          │
- * └──────────────────┴──────────────────────┘
- */
 @Service
 public class IdentityHidingServiceImpl implements IdentityHidingService {
 
     private static final Logger logger = LoggerFactory.getLogger(IdentityHidingServiceImpl.class);
 
-    private static final String CONFIG_TABLE = "lobster_identity_hiding_config";
-
-    /** 暴露身份的关键词→替换词映射(默认规则) */
     private static final Map<Pattern, String> DEFAULT_REPLACEMENTS = new LinkedHashMap<>();
 
     static {
@@ -67,7 +43,6 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         DEFAULT_REPLACEMENTS.put(Pattern.compile("销冠助手"), "您的顾问");
     }
 
-    /** 安全的系统提示词模板 */
     private static final String SAFE_SYSTEM_PROMPT =
             "你是一位专业、热情、耐心的顾问。" +
             "你的目标是通过专业对话帮助客户解决问题,建立信任。" +
@@ -77,7 +52,6 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
             "不要使用任何暗示你是AI、机器人或自动系统的措辞。" +
             "回复要像真人一样自然,可以适当使用语气词和口语表达。";
 
-    /** 安全的转人工话术模板(无感转人工,不暴露机器人身份) */
     private static final Map<String, String> SAFE_HANDOFF_MESSAGES = new LinkedHashMap<>();
 
     static {
@@ -87,7 +61,6 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         SAFE_HANDOFF_MESSAGES.put("low", "我让更专业的同事来跟您详细聊聊");
     }
 
-    /** 安全的错误回复模板 */
     private static final Map<String, String> SAFE_ERROR_MESSAGES = new HashMap<>();
 
     static {
@@ -98,13 +71,12 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         SAFE_ERROR_MESSAGES.put("default", "我需要确认一下,请稍等");
     }
 
-    /** 租户自定义配置缓存 */
     private final Map<Long, Map<String, Object>> configCache = new ConcurrentHashMap<>();
     private final Map<Long, Long> cacheRefreshTime = new ConcurrentHashMap<>();
     private static final long CACHE_TTL_MS = 5 * 60 * 1000L;
 
     @Autowired
-    private JdbcTemplate jdbcTemplate;
+    private LobsterIdentityHidingConfigMapper identityHidingConfigMapper;
 
     @Override
     public String hideIdentity(Long companyId, String message, Map<String, Object> context) {
@@ -114,12 +86,10 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
 
         String result = message;
 
-        // 第一步:应用默认替换规则
         for (Map.Entry<Pattern, String> entry : DEFAULT_REPLACEMENTS.entrySet()) {
             result = entry.getKey().matcher(result).replaceAll(entry.getValue());
         }
 
-        // 第二步:应用租户自定义替换规则
         Map<String, Object> config = getEffectiveConfig(companyId);
         Object customRules = config.get("customReplacements");
         if (customRules instanceof Map) {
@@ -130,12 +100,10 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
             }
         }
 
-        // 第三步:清理多余空白(替换后可能产生连续空格或空句)
         result = result.replaceAll("\\s{2,}", " ").trim();
         result = result.replaceAll("[,。]\\s*[,。]", ",");
         result = result.replaceAll("^\\s*[,。]\\s*", "");
 
-        // 第四步:如果替换后消息为空,返回安全的兜底回复
         if (result.trim().isEmpty()) {
             return "好的,我理解了,让我来帮您处理";
         }
@@ -146,27 +114,16 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
     @Override
     public String getSafeSystemPrompt(Long companyId) {
         Map<String, Object> config = getEffectiveConfig(companyId);
-
-        // 如果租户自定义了系统提示词,使用自定义的
         Object customPrompt = config.get("systemPrompt");
         if (customPrompt != null && !customPrompt.toString().isEmpty()) {
             return customPrompt.toString();
         }
-
-        // 获取租户的员工姓名,用于个性化
-        String advisorName = getAdvisorName(companyId);
-        if (advisorName != null) {
-            return SAFE_SYSTEM_PROMPT + "你的名字是" + advisorName + "。";
-        }
-
         return SAFE_SYSTEM_PROMPT;
     }
 
     @Override
     public String getSafeHandoffMessage(Long companyId, String urgency) {
         Map<String, Object> config = getEffectiveConfig(companyId);
-
-        // 检查租户自定义的转人工话术
         Object customMessages = config.get("handoffMessages");
         if (customMessages instanceof Map) {
             @SuppressWarnings("unchecked")
@@ -174,15 +131,12 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
             String msg = messages.get(urgency);
             if (msg != null) return msg;
         }
-
         return SAFE_HANDOFF_MESSAGES.getOrDefault(urgency, SAFE_HANDOFF_MESSAGES.get("medium"));
     }
 
     @Override
     public String getSafeErrorMessage(Long companyId, String errorType) {
         Map<String, Object> config = getEffectiveConfig(companyId);
-
-        // 检查租户自定义的错误回复
         Object customErrors = config.get("errorMessages");
         if (customErrors instanceof Map) {
             @SuppressWarnings("unchecked")
@@ -190,22 +144,15 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
             String msg = errors.get(errorType);
             if (msg != null) return msg;
         }
-
         return SAFE_ERROR_MESSAGES.getOrDefault(errorType, SAFE_ERROR_MESSAGES.get("default"));
     }
 
     @Override
     public long calculateHumanLikeDelay(String message, Map<String, Object> context) {
         if (message == null) return 500;
-
-        // 基础延迟:300~800ms(模拟看到消息的反应时间)
         long baseDelay = 300 + (long) (Math.random() * 500);
-
-        // 根据消息长度增加延迟(模拟打字时间)
         int charCount = message.length();
         long typingDelay = (long) (charCount * (30 + Math.random() * 40));
-
-        // 短消息快速回复,长消息适当延迟
         if (charCount < 20) {
             typingDelay = Math.min(typingDelay, 1500);
         } else if (charCount < 50) {
@@ -213,13 +160,10 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         } else {
             typingDelay = Math.min(typingDelay, 5000);
         }
-
-        // 如果是首轮对话,增加延迟(模拟思考)
         if (context != null && context.containsKey("isFirstMessage") &&
                 Boolean.TRUE.equals(context.get("isFirstMessage"))) {
             baseDelay += 500;
         }
-
         return baseDelay + typingDelay;
     }
 
@@ -230,16 +174,26 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
 
     @Override
     public void updateIdentityConfig(Long companyId, Map<String, Object> config) {
-        ensureTable();
         try {
             for (Map.Entry<String, Object> entry : config.entrySet()) {
                 String value = entry.getValue() instanceof String ?
                         (String) entry.getValue() : JSON.toJSONString(entry.getValue());
-                jdbcTemplate.update(
-                        "INSERT INTO " + CONFIG_TABLE + " (company_id, config_key, config_value, update_time) " +
-                                "VALUES (?, ?, ?, NOW()) " +
-                                "ON DUPLICATE KEY UPDATE config_value = ?, update_time = NOW()",
-                        companyId, entry.getKey(), value, value);
+
+                LambdaQueryWrapper<LobsterIdentityHidingConfig> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(LobsterIdentityHidingConfig::getCompanyId, companyId)
+                       .eq(LobsterIdentityHidingConfig::getConfigKey, entry.getKey());
+                LobsterIdentityHidingConfig existing = identityHidingConfigMapper.selectOne(wrapper);
+
+                LobsterIdentityHidingConfig entity = new LobsterIdentityHidingConfig();
+                entity.setCompanyId(companyId);
+                entity.setConfigKey(entry.getKey());
+                entity.setConfigValue(value);
+                if (existing != null) {
+                    entity.setId(existing.getId());
+                    identityHidingConfigMapper.updateById(entity);
+                } else {
+                    identityHidingConfigMapper.insert(entity);
+                }
             }
             configCache.remove(companyId);
             cacheRefreshTime.remove(companyId);
@@ -248,26 +202,6 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         }
     }
 
-    /**
-     * 获取租户顾问姓名
-     * 从员工信息中获取,用于个性化系统提示词
-     */
-    private String getAdvisorName(Long companyId) {
-        try {
-            String sql = "SELECT nick_name FROM company_user WHERE company_id = ? AND status = 0 LIMIT 1";
-            List<String> names = jdbcTemplate.queryForList(sql, String.class, companyId);
-            if (!names.isEmpty() && names.get(0) != null) {
-                return names.get(0);
-            }
-        } catch (Exception e) {
-            logger.debug("[IdentityHiding] 获取顾问姓名失败: {}", e.getMessage());
-        }
-        return null;
-    }
-
-    /**
-     * 获取有效的身份隐藏配置(带缓存)
-     */
     private Map<String, Object> getEffectiveConfig(Long companyId) {
         Long lastRefresh = cacheRefreshTime.get(companyId);
         if (lastRefresh != null && System.currentTimeMillis() - lastRefresh < CACHE_TTL_MS) {
@@ -275,24 +209,14 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
             if (cached != null) return cached;
         }
 
-        Map<String, Object> config = loadConfigFromDatabase(companyId);
-        configCache.put(companyId, config);
-        cacheRefreshTime.put(companyId, System.currentTimeMillis());
-        return config;
-    }
-
-    /**
-     * 从数据库加载租户配置
-     */
-    private Map<String, Object> loadConfigFromDatabase(Long companyId) {
         Map<String, Object> config = new HashMap<>();
         try {
-            ensureTable();
-            String sql = "SELECT config_key, config_value FROM " + CONFIG_TABLE + " WHERE company_id = ?";
-            List<Map<String, Object>> rows = jdbcTemplate.queryForList(sql, companyId);
-            for (Map<String, Object> row : rows) {
-                String key = (String) row.get("config_key");
-                String value = (String) row.get("config_value");
+            LambdaQueryWrapper<LobsterIdentityHidingConfig> wrapper = new LambdaQueryWrapper<>();
+            wrapper.eq(LobsterIdentityHidingConfig::getCompanyId, companyId);
+            List<LobsterIdentityHidingConfig> rows = identityHidingConfigMapper.selectList(wrapper);
+            for (LobsterIdentityHidingConfig row : rows) {
+                String key = row.getConfigKey();
+                String value = row.getConfigValue();
                 try {
                     config.put(key, JSON.parse(value));
                 } catch (Exception e) {
@@ -302,23 +226,9 @@ public class IdentityHidingServiceImpl implements IdentityHidingService {
         } catch (Exception e) {
             logger.debug("[IdentityHiding] 加载配置失败: companyId={}", companyId);
         }
-        return config;
-    }
 
-    private void ensureTable() {
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM " + CONFIG_TABLE + " LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS " + CONFIG_TABLE + " (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT NOT NULL, " +
-                            "config_key VARCHAR(100) NOT NULL, " +
-                            "config_value TEXT, " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "update_time DATETIME DEFAULT NOW(), " +
-                            "UNIQUE KEY uk_company_key (company_id, config_key)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-        }
+        configCache.put(companyId, config);
+        cacheRefreshTime.put(companyId, System.currentTimeMillis());
+        return config;
     }
 }

+ 0 - 33
fs-service/src/main/java/com/fs/company/service/workflow/impl/ComplianceServiceImpl.java

@@ -6,7 +6,6 @@ import com.fs.company.service.workflow.ComplianceService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.*;
@@ -21,9 +20,6 @@ public class ComplianceServiceImpl implements ComplianceService {
     @Autowired
     private LobsterComplianceRuleMapper complianceRuleMapper;
 
-    @Autowired(required = false)
-    private JdbcTemplate jdbcTemplate;
-
     @Override
     public boolean checkCompliance(Long companyId, String content) {
         return checkComplianceWithResult(companyId, content).isCompliant();
@@ -125,8 +121,6 @@ public class ComplianceServiceImpl implements ComplianceService {
             return ComplianceCheckResult.ok();
         }
 
-        ensureTable();
-
         List<LobsterComplianceRule> rules = complianceRuleMapper.selectEnabledByCompanyId(companyId);
 
         if (industryType != null && !industryType.isEmpty()) {
@@ -198,31 +192,4 @@ public class ComplianceServiceImpl implements ComplianceService {
         }
         return null;
     }
-
-    private void ensureTable() {
-        if (jdbcTemplate == null) return;
-        try {
-            jdbcTemplate.queryForObject("SELECT 1 FROM lobster_compliance_rule LIMIT 1", Integer.class);
-        } catch (Exception e) {
-            jdbcTemplate.execute(
-                    "CREATE TABLE IF NOT EXISTS lobster_compliance_rule (" +
-                            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-                            "company_id BIGINT DEFAULT NULL, " +
-                            "rule_name VARCHAR(200) NOT NULL, " +
-                            "rule_type VARCHAR(50) DEFAULT 'keyword', " +
-                            "pattern TEXT, " +
-                            "description VARCHAR(500) DEFAULT NULL, " +
-                            "action VARCHAR(50) DEFAULT 'warn', " +
-                            "severity INT DEFAULT 1, " +
-                            "enabled INT DEFAULT 1, " +
-                            "del_flag INT DEFAULT 0, " +
-                            "create_by VARCHAR(64) DEFAULT NULL, " +
-                            "create_time DATETIME DEFAULT NOW(), " +
-                            "update_by VARCHAR(64) DEFAULT NULL, " +
-                            "update_time DATETIME DEFAULT NOW(), " +
-                            "INDEX idx_company (company_id)" +
-                            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
-            logger.info("[Compliance] 自动创建了lobster_compliance_rule表");
-        }
-    }
 }

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů