boss před 2 dny
rodič
revize
58eae4ec77
21 změnil soubory, kde provedl 601 přidání a 42 odebrání
  1. 2 0
      fs-service/src/main/java/com/fs/company/domain/CompanyWorkflowLobsterTask.java
  2. 64 0
      fs-service/src/main/java/com/fs/company/domain/LobsterE2eRun.java
  3. 64 0
      fs-service/src/main/java/com/fs/company/domain/LobsterE2eRunNode.java
  4. 17 7
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionLog.java
  5. 16 5
      fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionSuggestion.java
  6. 6 0
      fs-service/src/main/java/com/fs/company/domain/LobsterNodeExecutionLog.java
  7. 58 0
      fs-service/src/main/java/com/fs/company/domain/LobsterTestScenario.java
  8. 28 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterE2eRunMapper.java
  9. 19 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterE2eRunNodeMapper.java
  10. 3 6
      fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java
  11. 23 0
      fs-service/src/main/java/com/fs/company/mapper/LobsterTestScenarioMapper.java
  12. 7 5
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterMapper.xml
  13. 9 5
      fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterTaskMapper.xml
  14. 9 6
      fs-service/src/main/resources/mapper/company/LobsterNodeExecutionLogMapper.xml
  15. 6 2
      fs-service/src/main/resources/mapper/company/LobsterWorkflowInstanceMapper.xml
  16. 70 0
      fs-service/src/main/resources/mapper/lobster/LobsterE2eRunMapper.xml
  17. 59 0
      fs-service/src/main/resources/mapper/lobster/LobsterE2eRunNodeMapper.xml
  18. 6 6
      fs-service/src/main/resources/mapper/lobster/LobsterEvolutionConfigMapper.xml
  19. 54 0
      fs-service/src/main/resources/mapper/lobster/LobsterEvolutionSuggestionMapper.xml
  20. 64 0
      fs-service/src/main/resources/mapper/lobster/LobsterTestScenarioMapper.xml
  21. 17 0
      sql/lobster_model_upgrade_patch.sql

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

@@ -86,4 +86,6 @@ public class CompanyWorkflowLobsterTask extends BaseEntity {
     private Long qwUserId; // 企微用户id
 
     private Long bindingId;
+
+    private String externalUserId; // 企微外部联系人ID
 }

+ 64 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterE2eRun.java

@@ -0,0 +1,64 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 龙虾E2E测试运行头表
+ */
+@Data
+@TableName("lobster_e2e_run")
+public class LobsterE2eRun {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 运行ID(UUID) */
+    private String runId;
+
+    private Long companyId;
+
+    /** 工作流模板ID */
+    private Long templateId;
+
+    /** 工作流实例ID */
+    private Long instanceId;
+
+    /** 测试场景ID */
+    private Long scenarioId;
+
+    /** 业务描述(即时生成时) */
+    private String businessDesc;
+
+    /** 综合评分(0-100) */
+    private BigDecimal totalScore;
+
+    /** 通过节点数 */
+    private Integer passedNodeCnt;
+
+    /** 总节点数 */
+    private Integer totalNodeCnt;
+
+    /** 总耗时ms */
+    private Long durationMs;
+
+    /** RUNNING|SUCCESS|FAILED */
+    private String status;
+
+    /** 错误信息 */
+    private String errorMsg;
+
+    /** 生成的进化建议数 */
+    private Integer evolutionCount;
+
+    private String createBy;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+}

+ 64 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterE2eRunNode.java

@@ -0,0 +1,64 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 龙虾E2E测试节点明细
+ */
+@Data
+@TableName("lobster_e2e_run_node")
+public class LobsterE2eRunNode {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 关联 lobster_e2e_run.run_id */
+    private String runId;
+
+    /** 节点序号 */
+    private Integer nodeSeq;
+
+    /** 节点编码 */
+    private String nodeCode;
+
+    /** 节点类型 */
+    private String nodeType;
+
+    private String nodeName;
+
+    /** 单节点轮次(多轮对话) */
+    private Integer turnNo;
+
+    /** 用户输入 */
+    private String userInput;
+
+    /** AI输出 */
+    private String aiOutput;
+
+    /** 本节点本轮评分 */
+    private BigDecimal score;
+
+    /** 维度评分JSON */
+    private String scoreDetail;
+
+    private Long durationMs;
+
+    /** 使用的模型 */
+    private String modelUsed;
+
+    /** 进化建议草稿 */
+    private String evolutionHint;
+
+    /** 0=未达标 1=达标 */
+    private Integer passed;
+
+    private String errorMsg;
+
+    private LocalDateTime createTime;
+}

+ 17 - 7
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionLog.java

@@ -6,6 +6,9 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import java.time.LocalDateTime;
 
+/**
+ * 龙虾进化交互日志
+ */
 @Data
 @TableName("lobster_evolution_log")
 public class LobsterEvolutionLog {
@@ -13,13 +16,20 @@ public class LobsterEvolutionLog {
     private Long id;
     private Long companyId;
     private Long workflowId;
-    private String actionType;
+    private Long instanceId;
+    private Long contactId;
+    /** 通道类型: QW/WX/IM */
+    private String channelType;
     private String nodeCode;
-    private String beforeContent;
-    private String afterContent;
-    private String changeDesc;
-    private Double improvementRate;
-    private String status;
-    private LocalDateTime evolveTime;
+    /** 发送的消息 */
+    private String sentMessage;
+    /** 客户回复 */
+    private String customerReply;
+    /** 交互结果: purchase/inquiry/complaint/positive/negative/schedule/other */
+    private String outcome;
+    /** 变量快照 JSON */
+    private String variables;
+    /** 响应时长(毫秒) */
+    private Long durationMs;
     private LocalDateTime createTime;
 }

+ 16 - 5
fs-service/src/main/java/com/fs/company/domain/LobsterEvolutionSuggestion.java

@@ -6,6 +6,9 @@ import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import java.time.LocalDateTime;
 
+/**
+ * 龙虾进化优化建议
+ */
 @Data
 @TableName("lobster_evolution_suggestion")
 public class LobsterEvolutionSuggestion {
@@ -15,10 +18,18 @@ public class LobsterEvolutionSuggestion {
     private Long workflowId;
     private String nodeCode;
     private String suggestionType;
-    private String suggestionContent;
-    private String originalContent;
-    private Double confidenceScore;
-    private String status;
-    private String createdBy;
+    /** 当前内容(对应 DDL current_content) */
+    private String currentContent;
+    /** 建议内容(对应 DDL suggested_content) */
+    private String suggestedContent;
+    /** 置信度 */
+    private Double confidence;
+    /** 优化原因 */
+    private String reason;
+    /** 相关指标 JSON */
+    private String metrics;
+    /** 状态: 0待处理 1已应用 2已忽略 */
+    private Integer status;
+    private LocalDateTime applyTime;
     private LocalDateTime createTime;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterNodeExecutionLog.java

@@ -21,6 +21,9 @@ public class LobsterNodeExecutionLog extends BaseEntity {
 
     private Long workflowId;
 
+    /** 节点编码(用于按节点维度统计) */
+    private String nodeCode;
+
     private Integer nodeIndex;
 
     private String nodeType;
@@ -43,5 +46,8 @@ public class LobsterNodeExecutionLog extends BaseEntity {
 
     private Integer retryCount;
 
+    /** 质量评分(0-100) */
+    private Integer qualityScore;
+
     private Integer delFlag;
 }

+ 58 - 0
fs-service/src/main/java/com/fs/company/domain/LobsterTestScenario.java

@@ -0,0 +1,58 @@
+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.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * 龙虾测试场景剧本(数据驱动回归)
+ */
+@Data
+@TableName("lobster_test_scenario")
+public class LobsterTestScenario {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long companyId;
+
+    /** 场景名 */
+    private String scenarioName;
+
+    /** 业务描述(留空则用 template_id) */
+    private String businessDesc;
+
+    /** 关联工作流模板 */
+    private Long templateId;
+
+    /** 用户输入数组JSON */
+    private String userInputsJson;
+
+    /** 期望命中的节点编码JSON */
+    private String expectedNodes;
+
+    /** 最低通过分 */
+    private BigDecimal minScore;
+
+    /** 是否启用回归 */
+    private Integer enabled;
+
+    /** 自定义cron(空则跟随全局) */
+    private String cron;
+
+    private String lastRunId;
+
+    private String lastRunStatus;
+
+    private LocalDateTime lastRunTime;
+
+    private String createBy;
+
+    private LocalDateTime createTime;
+
+    private LocalDateTime updateTime;
+}

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

@@ -0,0 +1,28 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterE2eRun;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 龙虾E2E测试运行Mapper
+ */
+public interface LobsterE2eRunMapper extends BaseMapper<LobsterE2eRun> {
+
+    int insertRun(LobsterE2eRun run);
+
+    LobsterE2eRun selectByRunId(@Param("runId") String runId);
+
+    List<LobsterE2eRun> selectByCompanyId(@Param("companyId") Long companyId);
+
+    int updateStatus(@Param("runId") String runId,
+                     @Param("status") String status,
+                     @Param("totalScore") java.math.BigDecimal totalScore,
+                     @Param("passedNodeCnt") Integer passedNodeCnt,
+                     @Param("totalNodeCnt") Integer totalNodeCnt,
+                     @Param("durationMs") Long durationMs,
+                     @Param("evolutionCount") Integer evolutionCount,
+                     @Param("errorMsg") String errorMsg);
+}

+ 19 - 0
fs-service/src/main/java/com/fs/company/mapper/LobsterE2eRunNodeMapper.java

@@ -0,0 +1,19 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterE2eRunNode;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 龙虾E2E测试节点明细Mapper
+ */
+public interface LobsterE2eRunNodeMapper extends BaseMapper<LobsterE2eRunNode> {
+
+    int insertNode(LobsterE2eRunNode node);
+
+    int batchInsert(@Param("list") List<LobsterE2eRunNode> list);
+
+    List<LobsterE2eRunNode> selectByRunId(@Param("runId") String runId);
+}

+ 3 - 6
fs-service/src/main/java/com/fs/company/mapper/LobsterEvolutionSuggestionMapper.java

@@ -12,18 +12,15 @@ public interface LobsterEvolutionSuggestionMapper extends BaseMapper<LobsterEvol
     @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'")
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 0")
     Integer countPendingByCompanyId(@Param("companyId") Long companyId);
 
-    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'applied'")
+    @Select("SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 1")
     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}")
+    @Update("UPDATE lobster_evolution_suggestion SET status = 1, apply_time = NOW() WHERE id = #{id}")
     int markApplied(@Param("id") Long id);
 }

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

@@ -0,0 +1,23 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.LobsterTestScenario;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 龙虾测试场景剧本Mapper
+ */
+public interface LobsterTestScenarioMapper extends BaseMapper<LobsterTestScenario> {
+
+    int insertScenario(LobsterTestScenario scenario);
+
+    LobsterTestScenario selectById(@Param("id") Long id);
+
+    List<LobsterTestScenario> selectEnabledByCompanyId(@Param("companyId") Long companyId);
+
+    int updateLastRun(@Param("id") Long id,
+                      @Param("lastRunId") String lastRunId,
+                      @Param("lastRunStatus") String lastRunStatus);
+}

+ 7 - 5
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterMapper.xml

@@ -16,11 +16,12 @@
         <result property="updateBy" column="update_by"/>
         <result property="updateTime" column="update_time"/>
         <result property="delFlag" column="del_flag"/>
+        <result property="canvasData" column="canvas_data"/>
     </resultMap>
 
     <select id="selectTemplateList" resultMap="CompanyWorkflowLobsterResult">
         select id, company_id, template_code, template_name, industry_type, description, status, version,
-               create_by, create_time, update_by, update_time, del_flag
+               create_by, create_time, update_by, update_time, del_flag, canvas_data
         from company_workflow_lobster
         where del_flag = 0
           and company_id = #{companyId}
@@ -29,14 +30,14 @@
 
     <insert id="insertTemplate" useGeneratedKeys="true" keyProperty="id">
         insert into company_workflow_lobster
-        (company_id, template_code, template_name, industry_type, description, status, version, create_by, create_time, update_by, update_time, del_flag)
+        (company_id, template_code, template_name, industry_type, description, status, version, create_by, create_time, update_by, update_time, del_flag, canvas_data)
         values
-        (#{companyId}, #{templateCode}, #{templateName}, #{industryType}, #{description}, #{status}, #{version}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{delFlag})
+        (#{companyId}, #{templateCode}, #{templateName}, #{industryType}, #{description}, #{status}, #{version}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{delFlag}, #{canvasData})
     </insert>
 
     <select id="selectTemplateByIdAndCompanyId" resultMap="CompanyWorkflowLobsterResult">
         select id, company_id, template_code, template_name, industry_type, description, status, version,
-               create_by, create_time, update_by, update_time, del_flag
+               create_by, create_time, update_by, update_time, del_flag, canvas_data
         from company_workflow_lobster
         where id = #{id}
           and company_id = #{companyId}
@@ -45,7 +46,7 @@
     </select>
     <select id="selectTemplateListByStatus" resultType="com.fs.company.domain.CompanyWorkflowLobster">
         select id, company_id, template_code, template_name, industry_type, description, status, version,
-               create_by, create_time, update_by, update_time, del_flag
+               create_by, create_time, update_by, update_time, del_flag, canvas_data
         from company_workflow_lobster
         where del_flag = 0
           and company_id = #{companyId}
@@ -63,6 +64,7 @@
             <if test="version != null">version = #{version},</if>
             <if test="updateBy != null">update_by = #{updateBy},</if>
             <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="canvasData != null">canvas_data = #{canvasData},</if>
         </trim>
         where id = #{id}
           and company_id = #{companyId}

+ 9 - 5
fs-service/src/main/resources/mapper/company/CompanyWorkflowLobsterTaskMapper.xml

@@ -22,6 +22,10 @@
         <result property="corpId"          column="corp_id"/>
         <result property="companyUserId"   column="company_user_id"/>
         <result property="lobsterNodeId"   column="lobster_node_id"/>
+        <result property="sendTime"        column="send_time"/>
+        <result property="qwUserId"        column="qw_user_id"/>
+        <result property="bindingId"       column="binding_id"/>
+        <result property="externalUserId"  column="external_user_id"/>
         <result property="remark"          column="remark"/>
         <result property="createBy"        column="create_by"/>
         <result property="createTime"      column="create_time"/>
@@ -33,7 +37,7 @@
         select id, company_id, template_id, task_name, task_type, task_content,
                cron_expression, execute_status, execute_count, max_retry, retry_count,
                last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
-               corp_id, company_user_id, lobster_node_id, remark,
+               corp_id, company_user_id, lobster_node_id, send_time, qw_user_id, binding_id, external_user_id, remark,
                create_by, create_time, update_by, update_time
         from company_workflow_lobster_task
         where id = #{id}
@@ -46,7 +50,7 @@
         select id, company_id, template_id, task_name, task_type, task_content,
                cron_expression, execute_status, execute_count, max_retry, retry_count,
                last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
-               corp_id, company_user_id, lobster_node_id, remark,
+               corp_id, company_user_id, lobster_node_id, send_time, qw_user_id, binding_id, external_user_id, remark,
                create_by, create_time, update_by, update_time
         from company_workflow_lobster_task
         where template_id = #{templateId}
@@ -59,7 +63,7 @@
         select id, company_id, template_id, task_name, task_type, task_content,
                cron_expression, execute_status, execute_count, max_retry, retry_count,
                last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
-               corp_id, company_user_id, lobster_node_id, remark,
+               corp_id, company_user_id, lobster_node_id, send_time, qw_user_id, binding_id, external_user_id, remark,
                create_by, create_time, update_by, update_time
         from company_workflow_lobster_task
         where company_id = #{companyId}
@@ -75,13 +79,13 @@
         (company_id, template_id, task_name, task_type, task_content, cron_expression,
          execute_status, execute_count, max_retry, retry_count,
          last_execute_time, next_execute_time, fail_reason, sort_order, del_flag,
-         corp_id, company_user_id, lobster_node_id, remark,
+         corp_id, company_user_id, lobster_node_id, send_time, qw_user_id, binding_id, external_user_id, remark,
          create_by, create_time, update_by, update_time)
         values
         (#{companyId}, #{templateId}, #{taskName}, #{taskType}, #{taskContent}, #{cronExpression},
          #{executeStatus}, #{executeCount}, #{maxRetry}, #{retryCount},
          #{lastExecuteTime}, #{nextExecuteTime}, #{failReason}, #{sortOrder}, #{delFlag},
-         #{corpId}, #{companyUserId}, #{lobsterNodeId}, #{remark},
+         #{corpId}, #{companyUserId}, #{lobsterNodeId}, #{sendTime}, #{qwUserId}, #{bindingId}, #{externalUserId}, #{remark},
          #{createBy}, #{createTime}, #{updateBy}, #{updateTime})
     </insert>
 

+ 9 - 6
fs-service/src/main/resources/mapper/company/LobsterNodeExecutionLogMapper.xml

@@ -7,6 +7,7 @@
         <result property="companyId" column="company_id"/>
         <result property="instanceId" column="instance_id"/>
         <result property="workflowId" column="workflow_id"/>
+        <result property="nodeCode" column="node_code"/>
         <result property="nodeIndex" column="node_index"/>
         <result property="nodeType" column="node_type"/>
         <result property="nodeName" column="node_name"/>
@@ -18,6 +19,7 @@
         <result property="tokenUsage" column="token_usage"/>
         <result property="errorMessage" column="error_message"/>
         <result property="retryCount" column="retry_count"/>
+        <result property="qualityScore" column="quality_score"/>
         <result property="delFlag" column="del_flag"/>
         <result property="createBy" column="create_by"/>
         <result property="createTime" column="create_time"/>
@@ -26,9 +28,9 @@
     </resultMap>
 
     <sql id="selectVo">
-        select id, company_id, instance_id, workflow_id, node_index, node_type, node_name,
+        select id, company_id, instance_id, workflow_id, node_code, node_index, node_type, node_name,
                input_content, output_content, ai_model, status, duration_ms, token_usage,
-               error_message, retry_count, del_flag, create_by, create_time, update_by, update_time
+               error_message, retry_count, quality_score, del_flag, create_by, create_time, update_by, update_time
         from lobster_node_execution_log
     </sql>
 
@@ -46,13 +48,13 @@
 
     <insert id="insert" useGeneratedKeys="true" keyProperty="id">
         insert into lobster_node_execution_log (
-            company_id, instance_id, workflow_id, node_index, node_type, node_name,
+            company_id, instance_id, workflow_id, node_code, node_index, node_type, node_name,
             input_content, output_content, ai_model, status, duration_ms, token_usage,
-            error_message, retry_count, del_flag, create_by, create_time, update_by, update_time
+            error_message, retry_count, quality_score, del_flag, create_by, create_time, update_by, update_time
         ) values (
-            #{companyId}, #{instanceId}, #{workflowId}, #{nodeIndex}, #{nodeType}, #{nodeName},
+            #{companyId}, #{instanceId}, #{workflowId}, #{nodeCode}, #{nodeIndex}, #{nodeType}, #{nodeName},
             #{inputContent}, #{outputContent}, #{aiModel}, #{status}, #{durationMs}, #{tokenUsage},
-            #{errorMessage}, #{retryCount}, #{delFlag}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}
+            #{errorMessage}, #{retryCount}, #{qualityScore}, #{delFlag}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}
         )
     </insert>
 
@@ -65,6 +67,7 @@
             <if test="tokenUsage != null">token_usage = #{tokenUsage},</if>
             <if test="errorMessage != null">error_message = #{errorMessage},</if>
             <if test="retryCount != null">retry_count = #{retryCount},</if>
+            <if test="qualityScore != null">quality_score = #{qualityScore},</if>
             <if test="updateBy != null">update_by = #{updateBy},</if>
             update_time = #{updateTime}
         </set>

+ 6 - 2
fs-service/src/main/resources/mapper/company/LobsterWorkflowInstanceMapper.xml

@@ -70,12 +70,12 @@
     <insert id="insert" useGeneratedKeys="true" keyProperty="id">
         insert into lobster_workflow_instance (
             company_id, workflow_id, instance_name, status, contact_id, contact_name,
-            current_node_index, current_node_name, total_nodes, completed_nodes,
+            channel_type, current_node_index, current_node_name, total_nodes, completed_nodes,
             context_snapshot, variables, start_time, end_time, last_activity_time,
             error_message, del_flag, create_by, create_time, update_by, update_time
         ) values (
             #{companyId}, #{workflowId}, #{instanceName}, #{status}, #{contactId}, #{contactName},
-            #{currentNodeIndex}, #{currentNodeName}, #{totalNodes}, #{completedNodes},
+            #{channelType}, #{currentNodeIndex}, #{currentNodeName}, #{totalNodes}, #{completedNodes},
             #{contextSnapshot}, #{variables}, #{startTime}, #{endTime}, #{lastActivityTime},
             #{errorMessage}, #{delFlag}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}
         )
@@ -85,6 +85,7 @@
         update lobster_workflow_instance
         <set>
             <if test="status != null">status = #{status},</if>
+            <if test="channelType != null">channel_type = #{channelType},</if>
             <if test="currentNodeIndex != null">current_node_index = #{currentNodeIndex},</if>
             <if test="currentNodeName != null">current_node_name = #{currentNodeName},</if>
             <if test="completedNodes != null">completed_nodes = #{completedNodes},</if>
@@ -93,6 +94,9 @@
             <if test="endTime != null">end_time = #{endTime},</if>
             <if test="lastActivityTime != null">last_activity_time = #{lastActivityTime},</if>
             <if test="errorMessage != null">error_message = #{errorMessage},</if>
+            <if test="controlMode != null">control_mode = #{controlMode},</if>
+            <if test="controlUpdatedBy != null">control_updated_by = #{controlUpdatedBy},</if>
+            <if test="controlUpdatedAt != null">control_updated_at = #{controlUpdatedAt},</if>
             <if test="updateBy != null">update_by = #{updateBy},</if>
             update_time = #{updateTime}
         </set>

+ 70 - 0
fs-service/src/main/resources/mapper/lobster/LobsterE2eRunMapper.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.LobsterE2eRunMapper">
+
+    <resultMap type="com.fs.company.domain.LobsterE2eRun" id="E2eRunResult">
+        <id property="id" column="id"/>
+        <result property="runId" column="run_id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="templateId" column="template_id"/>
+        <result property="instanceId" column="instance_id"/>
+        <result property="scenarioId" column="scenario_id"/>
+        <result property="businessDesc" column="business_desc"/>
+        <result property="totalScore" column="total_score"/>
+        <result property="passedNodeCnt" column="passed_node_cnt"/>
+        <result property="totalNodeCnt" column="total_node_cnt"/>
+        <result property="durationMs" column="duration_ms"/>
+        <result property="status" column="status"/>
+        <result property="errorMsg" column="error_msg"/>
+        <result property="evolutionCount" column="evolution_count"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        select id, run_id, company_id, template_id, instance_id, scenario_id,
+               business_desc, total_score, passed_node_cnt, total_node_cnt, duration_ms,
+               status, error_msg, evolution_count, create_by, create_time, update_time
+        from lobster_e2e_run
+    </sql>
+
+    <insert id="insertRun" useGeneratedKeys="true" keyProperty="id">
+        insert into lobster_e2e_run (
+            run_id, company_id, template_id, instance_id, scenario_id,
+            business_desc, total_score, passed_node_cnt, total_node_cnt, duration_ms,
+            status, error_msg, evolution_count, create_by, create_time
+        ) values (
+            #{runId}, #{companyId}, #{templateId}, #{instanceId}, #{scenarioId},
+            #{businessDesc}, #{totalScore}, #{passedNodeCnt}, #{totalNodeCnt}, #{durationMs},
+            #{status}, #{errorMsg}, #{evolutionCount}, #{createBy}, #{createTime}
+        )
+    </insert>
+
+    <select id="selectByRunId" resultMap="E2eRunResult">
+        <include refid="selectVo"/>
+        where run_id = #{runId}
+    </select>
+
+    <select id="selectByCompanyId" resultMap="E2eRunResult">
+        <include refid="selectVo"/>
+        where company_id = #{companyId}
+        order by create_time desc
+    </select>
+
+    <update id="updateStatus">
+        update lobster_e2e_run
+        <set>
+            <if test="status != null">status = #{status},</if>
+            <if test="totalScore != null">total_score = #{totalScore},</if>
+            <if test="passedNodeCnt != null">passed_node_cnt = #{passedNodeCnt},</if>
+            <if test="totalNodeCnt != null">total_node_cnt = #{totalNodeCnt},</if>
+            <if test="durationMs != null">duration_ms = #{durationMs},</if>
+            <if test="evolutionCount != null">evolution_count = #{evolutionCount},</if>
+            <if test="errorMsg != null">error_msg = #{errorMsg},</if>
+            update_time = NOW()
+        </set>
+        where run_id = #{runId}
+    </update>
+
+</mapper>

+ 59 - 0
fs-service/src/main/resources/mapper/lobster/LobsterE2eRunNodeMapper.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.LobsterE2eRunNodeMapper">
+
+    <resultMap type="com.fs.company.domain.LobsterE2eRunNode" id="E2eRunNodeResult">
+        <id property="id" column="id"/>
+        <result property="runId" column="run_id"/>
+        <result property="nodeSeq" column="node_seq"/>
+        <result property="nodeCode" column="node_code"/>
+        <result property="nodeType" column="node_type"/>
+        <result property="nodeName" column="node_name"/>
+        <result property="turnNo" column="turn_no"/>
+        <result property="userInput" column="user_input"/>
+        <result property="aiOutput" column="ai_output"/>
+        <result property="score" column="score"/>
+        <result property="scoreDetail" column="score_detail"/>
+        <result property="durationMs" column="duration_ms"/>
+        <result property="modelUsed" column="model_used"/>
+        <result property="evolutionHint" column="evolution_hint"/>
+        <result property="passed" column="passed"/>
+        <result property="errorMsg" column="error_msg"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <insert id="insertNode" useGeneratedKeys="true" keyProperty="id">
+        insert into lobster_e2e_run_node (
+            run_id, node_seq, node_code, node_type, node_name, turn_no,
+            user_input, ai_output, score, score_detail, duration_ms,
+            model_used, evolution_hint, passed, error_msg, create_time
+        ) values (
+            #{runId}, #{nodeSeq}, #{nodeCode}, #{nodeType}, #{nodeName}, #{turnNo},
+            #{userInput}, #{aiOutput}, #{score}, #{scoreDetail}, #{durationMs},
+            #{modelUsed}, #{evolutionHint}, #{passed}, #{errorMsg}, #{createTime}
+        )
+    </insert>
+
+    <insert id="batchInsert">
+        insert into lobster_e2e_run_node (
+            run_id, node_seq, node_code, node_type, node_name, turn_no,
+            user_input, ai_output, score, score_detail, duration_ms,
+            model_used, evolution_hint, passed, error_msg, create_time
+        ) values
+        <foreach collection="list" item="item" separator=",">
+            (#{item.runId}, #{item.nodeSeq}, #{item.nodeCode}, #{item.nodeType}, #{item.nodeName}, #{item.turnNo},
+             #{item.userInput}, #{item.aiOutput}, #{item.score}, #{item.scoreDetail}, #{item.durationMs},
+             #{item.modelUsed}, #{item.evolutionHint}, #{item.passed}, #{item.errorMsg}, #{item.createTime})
+        </foreach>
+    </insert>
+
+    <select id="selectByRunId" resultMap="E2eRunNodeResult">
+        select id, run_id, node_seq, node_code, node_type, node_name, turn_no,
+               user_input, ai_output, score, score_detail, duration_ms,
+               model_used, evolution_hint, passed, error_msg, create_time
+        from lobster_e2e_run_node
+        where run_id = #{runId}
+        order by node_seq asc, turn_no asc
+    </select>
+
+</mapper>

+ 6 - 6
fs-service/src/main/resources/mapper/lobster/LobsterEvolutionConfigMapper.xml

@@ -108,7 +108,7 @@
 
     <!-- === lobster_evolution_log === -->
     <insert id="insertEvolutionLog">
-        INSERT INTO lobster_evolution_log(company_id, workflow_id, node_code, action, detail, create_time)
+        INSERT INTO lobster_evolution_log(company_id, workflow_id, node_code, sent_message, outcome, create_time)
         VALUES(#{companyId}, #{workflowId}, #{nodeCode}, #{action}, #{detail}, NOW())
     </insert>
     <select id="selectEvolutionLogs" resultType="java.util.Map">
@@ -128,26 +128,26 @@
         INSERT INTO lobster_evolution_suggestion(company_id, workflow_id, node_code, suggestion_type,
             current_content, suggested_content, reason, confidence, status, create_time)
         VALUES(#{companyId}, #{workflowId}, #{nodeCode}, #{suggestionType},
-               #{currentContent}, #{suggestedContent}, #{reason}, #{confidence}, 'pending', NOW())
+               #{currentContent}, #{suggestedContent}, #{reason}, #{confidence}, 0, NOW())
     </insert>
     <update id="updateNodeMessage">
         UPDATE company_workflow_lobster_node SET message_template = #{suggestedContent}, update_time = NOW()
         WHERE workflow_id = #{workflowId} AND node_code = #{nodeCode}
     </update>
     <update id="acceptSuggestion">
-        UPDATE lobster_evolution_suggestion SET status = 'applied', apply_time = NOW() WHERE id = #{id}
+        UPDATE lobster_evolution_suggestion SET status = 1, apply_time = NOW() WHERE id = #{id}
     </update>
     <select id="countTotal" resultType="java.lang.Integer">
         SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId}
     </select>
     <select id="countReplied" resultType="java.lang.Integer">
-        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status IN ('reply','applied')
+        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status IN (1, 2)
     </select>
     <select id="countPendingSuggestion" resultType="java.lang.Integer">
-        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'pending'
+        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 0
     </select>
     <select id="countApplied" resultType="java.lang.Integer">
-        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 'applied'
+        SELECT COUNT(*) FROM lobster_evolution_suggestion WHERE company_id = #{companyId} AND status = 1
     </select>
     <select id="ensureSuggestionTable" resultType="java.lang.Integer">
         SELECT 1 FROM lobster_evolution_suggestion LIMIT 1

+ 54 - 0
fs-service/src/main/resources/mapper/lobster/LobsterEvolutionSuggestionMapper.xml

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.LobsterEvolutionSuggestionMapper">
+
+    <resultMap type="com.fs.company.domain.LobsterEvolutionSuggestion" id="EvolutionSuggestionResult">
+        <id property="id" column="id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="workflowId" column="workflow_id"/>
+        <result property="nodeCode" column="node_code"/>
+        <result property="suggestionType" column="suggestion_type"/>
+        <result property="currentContent" column="current_content"/>
+        <result property="suggestedContent" column="suggested_content"/>
+        <result property="confidence" column="confidence"/>
+        <result property="reason" column="reason"/>
+        <result property="metrics" column="metrics"/>
+        <result property="status" column="status"/>
+        <result property="applyTime" column="apply_time"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <select id="selectPendingByCompanyId" resultMap="EvolutionSuggestionResult">
+        select id, company_id, workflow_id, node_code, suggestion_type,
+               current_content, suggested_content, confidence, reason, metrics,
+               status, apply_time, create_time
+        from lobster_evolution_suggestion
+        where company_id = #{companyId} and status = 0
+        order by create_time desc
+        limit #{limit}
+    </select>
+
+    <insert id="insertSuggestion" useGeneratedKeys="true" keyProperty="id">
+        insert into lobster_evolution_suggestion (
+            company_id, workflow_id, node_code, suggestion_type,
+            current_content, suggested_content, confidence, reason, metrics,
+            status, create_time
+        ) values (
+            #{companyId}, #{workflowId}, #{nodeCode}, #{suggestionType},
+            #{currentContent}, #{suggestedContent}, #{confidence}, #{reason}, #{metrics},
+            0, #{createTime}
+        )
+    </insert>
+
+    <select id="selectByWorkflowAndNode" resultMap="EvolutionSuggestionResult">
+        select id, company_id, workflow_id, node_code, suggestion_type,
+               current_content, suggested_content, confidence, reason, metrics,
+               status, apply_time, create_time
+        from lobster_evolution_suggestion
+        where company_id = #{companyId}
+          and workflow_id = #{workflowId}
+          and node_code = #{nodeCode}
+        order by create_time desc
+    </select>
+
+</mapper>

+ 64 - 0
fs-service/src/main/resources/mapper/lobster/LobsterTestScenarioMapper.xml

@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.LobsterTestScenarioMapper">
+
+    <resultMap type="com.fs.company.domain.LobsterTestScenario" id="TestScenarioResult">
+        <id property="id" column="id"/>
+        <result property="companyId" column="company_id"/>
+        <result property="scenarioName" column="scenario_name"/>
+        <result property="businessDesc" column="business_desc"/>
+        <result property="templateId" column="template_id"/>
+        <result property="userInputsJson" column="user_inputs_json"/>
+        <result property="expectedNodes" column="expected_nodes"/>
+        <result property="minScore" column="min_score"/>
+        <result property="enabled" column="enabled"/>
+        <result property="cron" column="cron"/>
+        <result property="lastRunId" column="last_run_id"/>
+        <result property="lastRunStatus" column="last_run_status"/>
+        <result property="lastRunTime" column="last_run_time"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <insert id="insertScenario" useGeneratedKeys="true" keyProperty="id">
+        insert into lobster_test_scenario (
+            company_id, scenario_name, business_desc, template_id,
+            user_inputs_json, expected_nodes, min_score, enabled, cron,
+            create_by, create_time
+        ) values (
+            #{companyId}, #{scenarioName}, #{businessDesc}, #{templateId},
+            #{userInputsJson}, #{expectedNodes}, #{minScore}, #{enabled}, #{cron},
+            #{createBy}, #{createTime}
+        )
+    </insert>
+
+    <select id="selectById" resultMap="TestScenarioResult">
+        select id, company_id, scenario_name, business_desc, template_id,
+               user_inputs_json, expected_nodes, min_score, enabled, cron,
+               last_run_id, last_run_status, last_run_time,
+               create_by, create_time, update_time
+        from lobster_test_scenario
+        where id = #{id}
+    </select>
+
+    <select id="selectEnabledByCompanyId" resultMap="TestScenarioResult">
+        select id, company_id, scenario_name, business_desc, template_id,
+               user_inputs_json, expected_nodes, min_score, enabled, cron,
+               last_run_id, last_run_status, last_run_time,
+               create_by, create_time, update_time
+        from lobster_test_scenario
+        where company_id = #{companyId} and enabled = 1
+        order by create_time desc
+    </select>
+
+    <update id="updateLastRun">
+        update lobster_test_scenario
+        set last_run_id = #{lastRunId},
+            last_run_status = #{lastRunStatus},
+            last_run_time = NOW(),
+            update_time = NOW()
+        where id = #{id}
+    </update>
+
+</mapper>

+ 17 - 0
sql/lobster_model_upgrade_patch.sql

@@ -0,0 +1,17 @@
+-- ============================================================================
+-- 龙虾引擎模型升级补丁
+-- 修复: P0 canvasData缺失, P0 Task字段缺失, P2 NodeExecutionLog缺失列
+-- 日期: 2026-06-05
+-- ============================================================================
+
+-- 1. lobster_node_execution_log 增加 node_code 和 quality_score 列
+ALTER TABLE `lobster_node_execution_log`
+    ADD COLUMN `node_code` varchar(100) DEFAULT NULL COMMENT '节点编码' AFTER `workflow_id`,
+    ADD COLUMN `quality_score` int DEFAULT NULL COMMENT '质量评分(0-100)' AFTER `retry_count`;
+
+-- 2. company_workflow_lobster_task 增加 external_user_id 列(如尚未存在)
+ALTER TABLE `company_workflow_lobster_task`
+    ADD COLUMN IF NOT EXISTS `external_user_id` varchar(255) DEFAULT NULL COMMENT '外部联系人ID' AFTER `binding_id`;
+
+-- 3. 为 lobster_node_execution_log 增加按节点统计的索引
+CREATE INDEX IF NOT EXISTS `idx_exec_log_company_node` ON `lobster_node_execution_log` (`company_id`, `node_code`);