فهرست منبع

增加sop任务复制和节点转发功能

boweniac 7 ماه پیش
والد
کامیت
faef82b9ba
37فایلهای تغییر یافته به همراه971 افزوده شده و 21 حذف شده
  1. 1 1
      crontask/send_wx.go
  2. 1 0
      crontask/send_wx_on_timeout.go
  3. 5 0
      desc/base.api
  4. 6 0
      desc/wechat/sop_stage.api
  5. 15 0
      desc/wechat/sop_task.api
  6. 5 0
      ent/custom_types/types.go
  7. 4 2
      ent/migrate/schema.go
  8. 148 2
      ent/mutation.go
  9. 1 1
      ent/runtime/runtime.go
  10. 3 0
      ent/schema/sop_node.go
  11. 3 0
      ent/schema/sop_stage.go
  12. 48 0
      ent/set_not_nil.go
  13. 14 1
      ent/sopnode.go
  14. 3 0
      ent/sopnode/sopnode.go
  15. 10 0
      ent/sopnode/where.go
  16. 70 0
      ent/sopnode_create.go
  17. 36 0
      ent/sopnode_update.go
  18. 14 1
      ent/sopstage.go
  19. 3 0
      ent/sopstage/sopstage.go
  20. 10 0
      ent/sopstage/where.go
  21. 70 0
      ent/sopstage_create.go
  22. 36 0
      ent/sopstage_update.go
  23. 5 0
      internal/handler/routes.go
  24. 44 0
      internal/handler/sop_task/sop_task_copy_handler.go
  25. 37 3
      internal/logic/sop_node/create_sop_node_logic.go
  26. 19 0
      internal/logic/sop_node/get_sop_node_by_id_logic.go
  27. 19 0
      internal/logic/sop_node/get_sop_node_detail_logic.go
  28. 19 0
      internal/logic/sop_node/get_sop_node_list_logic.go
  29. 39 3
      internal/logic/sop_node/update_sop_node_logic.go
  30. 35 3
      internal/logic/sop_stage/create_sop_stage_logic.go
  31. 19 0
      internal/logic/sop_stage/get_sop_stage_by_id_logic.go
  32. 19 0
      internal/logic/sop_stage/get_sop_stage_detail_logic.go
  33. 19 0
      internal/logic/sop_stage/get_sop_stage_list_logic.go
  34. 39 3
      internal/logic/sop_stage/update_sop_stage_logic.go
  35. 7 1
      internal/logic/sop_task/get_sop_task_list_logic.go
  36. 126 0
      internal/logic/sop_task/sop_task_copy_logic.go
  37. 19 0
      internal/types/types.go

+ 1 - 1
crontask/send_wx.go

@@ -109,7 +109,7 @@ func (l *CronTask) sendWx() {
 		}
 
 		hookClient := hook.NewHook(serverIp, adminPort, wxPort)
-
+		l.Logger.Errorf("------------------------err--------------------------- %+v", err)
 		if v.ContentType == 1 {
 			err = hookClient.SendTextMsg(v.ContactWxid, v.Content)
 		} else {

+ 1 - 0
crontask/send_wx_on_timeout.go

@@ -85,6 +85,7 @@ func (l *CronTask) sendWxOnTimeout() {
 						SetSourceType(4).
 						SetSourceID(node.ID).
 						SetSubSourceID(uint64(i)).
+						SetOrganizationID(s.OrganizationID).
 						Save(ctx)
 				}
 			}

+ 5 - 0
desc/base.api

@@ -221,6 +221,11 @@ type Meta struct {
 	Filename    string    `json:"filename,optional"`
 }
 
+type ActionForward struct {
+	Wxid    string    `json:"wxid"`
+	Action []Action `json:"action"`
+}
+
 @server(
 	group: base
 )

+ 6 - 0
desc/wechat/sop_stage.api

@@ -29,6 +29,9 @@ type (
         // 命中后需要打的标签 
         ActionLabel  []uint64 `json:"actionLabel,optional"`
 
+        // 命中后转发的消息内容
+        ActionForward  *ActionForward `json:"actionForward,optional"`
+
         // 阶段顺序 
         IndexSort  *int `json:"indexSort,optional"`
 
@@ -102,6 +105,9 @@ type (
         // 命中后需要打的标签
         ActionLabel  []uint64 `json:"actionLabel,optional"`
 
+        // 命中后转发的消息内容
+        ActionForward  *ActionForward `json:"actionForward,optional"`
+
         // 阶段信息
         StageInfo  *SopStageInfo `json:"stageInfo,optional"`
     }

+ 15 - 0
desc/wechat/sop_task.api

@@ -76,6 +76,17 @@ type (
         // SopTask id | SopTask id
         Data uint64 `json:"data"`
     }
+
+    CopyReq {
+        // SopTask id | SopTask id
+        Id uint64 `json:"id"`
+
+        // SOP 任务名称
+        Name  *string `json:"name,optional"`
+
+        // Organization id | Organization id
+        OrganizationId uint64 `json:"organizationId,optional"`
+    }
 )
 
 @server(
@@ -124,4 +135,8 @@ service Wechat {
     // task stop | SopTask 停止
     @handler sopTaskStop
     post /sop_task/stop (IDReq) returns (BaseMsgResp)
+
+    // task start | SopTask 复制
+    @handler sopTaskCopy
+    post /sop_task/copy (CopyReq) returns (BaseMsgResp)
 }

+ 5 - 0
ent/custom_types/types.go

@@ -14,3 +14,8 @@ type Action struct {
 type Meta struct {
 	Filename string `json:"filename,omitempty"`
 }
+
+type ActionForward struct {
+	Wxid   string   `json:"wxid"`
+	Action []Action `json:"action"`
+}

+ 4 - 2
ent/migrate/schema.go

@@ -449,6 +449,7 @@ var (
 		{Name: "no_reply_condition", Type: field.TypeUint64, Comment: "超时触发时间(分钟)", Default: 0},
 		{Name: "action_message", Type: field.TypeJSON, Nullable: true, Comment: "命中后发送的消息内容"},
 		{Name: "action_label", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_forward", Type: field.TypeJSON, Nullable: true, Comment: "命中后转发的消息"},
 		{Name: "stage_id", Type: field.TypeUint64, Comment: "阶段 ID"},
 	}
 	// SopNodeTable holds the schema information for the "sop_node" table.
@@ -459,7 +460,7 @@ var (
 		ForeignKeys: []*schema.ForeignKey{
 			{
 				Symbol:     "sop_node_sop_stage_stage_nodes",
-				Columns:    []*schema.Column{SopNodeColumns[12]},
+				Columns:    []*schema.Column{SopNodeColumns[13]},
 				RefColumns: []*schema.Column{SopStageColumns[0]},
 				OnDelete:   schema.NoAction,
 			},
@@ -485,6 +486,7 @@ var (
 		{Name: "condition_list", Type: field.TypeJSON, Comment: "筛选条件列表"},
 		{Name: "action_message", Type: field.TypeJSON, Nullable: true, Comment: "命中后发送的消息内容"},
 		{Name: "action_label", Type: field.TypeJSON, Nullable: true, Comment: "命中后需要打的标签"},
+		{Name: "action_forward", Type: field.TypeJSON, Nullable: true, Comment: "命中后转发的消息"},
 		{Name: "index_sort", Type: field.TypeInt, Nullable: true, Comment: "阶段顺序", Default: 1},
 		{Name: "task_id", Type: field.TypeUint64, Comment: "SOP 任务 ID"},
 	}
@@ -496,7 +498,7 @@ var (
 		ForeignKeys: []*schema.ForeignKey{
 			{
 				Symbol:     "sop_stage_sop_task_task_stages",
-				Columns:    []*schema.Column{SopStageColumns[12]},
+				Columns:    []*schema.Column{SopStageColumns[13]},
 				RefColumns: []*schema.Column{SopTaskColumns[0]},
 				OnDelete:   schema.NoAction,
 			},

+ 148 - 2
ent/mutation.go

@@ -14780,6 +14780,7 @@ type SopNodeMutation struct {
 	appendaction_message  []custom_types.Action
 	action_label          *[]uint64
 	appendaction_label    []uint64
+	action_forward        **custom_types.ActionForward
 	clearedFields         map[string]struct{}
 	sop_stage             *uint64
 	clearedsop_stage      bool
@@ -15521,6 +15522,55 @@ func (m *SopNodeMutation) ResetActionLabel() {
 	delete(m.clearedFields, sopnode.FieldActionLabel)
 }
 
+// SetActionForward sets the "action_forward" field.
+func (m *SopNodeMutation) SetActionForward(ctf *custom_types.ActionForward) {
+	m.action_forward = &ctf
+}
+
+// ActionForward returns the value of the "action_forward" field in the mutation.
+func (m *SopNodeMutation) ActionForward() (r *custom_types.ActionForward, exists bool) {
+	v := m.action_forward
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionForward returns the old "action_forward" field's value of the SopNode entity.
+// If the SopNode object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SopNodeMutation) OldActionForward(ctx context.Context) (v *custom_types.ActionForward, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionForward is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionForward requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionForward: %w", err)
+	}
+	return oldValue.ActionForward, nil
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (m *SopNodeMutation) ClearActionForward() {
+	m.action_forward = nil
+	m.clearedFields[sopnode.FieldActionForward] = struct{}{}
+}
+
+// ActionForwardCleared returns if the "action_forward" field was cleared in this mutation.
+func (m *SopNodeMutation) ActionForwardCleared() bool {
+	_, ok := m.clearedFields[sopnode.FieldActionForward]
+	return ok
+}
+
+// ResetActionForward resets all changes to the "action_forward" field.
+func (m *SopNodeMutation) ResetActionForward() {
+	m.action_forward = nil
+	delete(m.clearedFields, sopnode.FieldActionForward)
+}
+
 // SetSopStageID sets the "sop_stage" edge to the SopStage entity by id.
 func (m *SopNodeMutation) SetSopStageID(id uint64) {
 	m.sop_stage = &id
@@ -15649,7 +15699,7 @@ func (m *SopNodeMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *SopNodeMutation) Fields() []string {
-	fields := make([]string, 0, 12)
+	fields := make([]string, 0, 13)
 	if m.created_at != nil {
 		fields = append(fields, sopnode.FieldCreatedAt)
 	}
@@ -15686,6 +15736,9 @@ func (m *SopNodeMutation) Fields() []string {
 	if m.action_label != nil {
 		fields = append(fields, sopnode.FieldActionLabel)
 	}
+	if m.action_forward != nil {
+		fields = append(fields, sopnode.FieldActionForward)
+	}
 	return fields
 }
 
@@ -15718,6 +15771,8 @@ func (m *SopNodeMutation) Field(name string) (ent.Value, bool) {
 		return m.ActionMessage()
 	case sopnode.FieldActionLabel:
 		return m.ActionLabel()
+	case sopnode.FieldActionForward:
+		return m.ActionForward()
 	}
 	return nil, false
 }
@@ -15751,6 +15806,8 @@ func (m *SopNodeMutation) OldField(ctx context.Context, name string) (ent.Value,
 		return m.OldActionMessage(ctx)
 	case sopnode.FieldActionLabel:
 		return m.OldActionLabel(ctx)
+	case sopnode.FieldActionForward:
+		return m.OldActionForward(ctx)
 	}
 	return nil, fmt.Errorf("unknown SopNode field %s", name)
 }
@@ -15844,6 +15901,13 @@ func (m *SopNodeMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetActionLabel(v)
 		return nil
+	case sopnode.FieldActionForward:
+		v, ok := value.(*custom_types.ActionForward)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionForward(v)
+		return nil
 	}
 	return fmt.Errorf("unknown SopNode field %s", name)
 }
@@ -15940,6 +16004,9 @@ func (m *SopNodeMutation) ClearedFields() []string {
 	if m.FieldCleared(sopnode.FieldActionLabel) {
 		fields = append(fields, sopnode.FieldActionLabel)
 	}
+	if m.FieldCleared(sopnode.FieldActionForward) {
+		fields = append(fields, sopnode.FieldActionForward)
+	}
 	return fields
 }
 
@@ -15969,6 +16036,9 @@ func (m *SopNodeMutation) ClearField(name string) error {
 	case sopnode.FieldActionLabel:
 		m.ClearActionLabel()
 		return nil
+	case sopnode.FieldActionForward:
+		m.ClearActionForward()
+		return nil
 	}
 	return fmt.Errorf("unknown SopNode nullable field %s", name)
 }
@@ -16013,6 +16083,9 @@ func (m *SopNodeMutation) ResetField(name string) error {
 	case sopnode.FieldActionLabel:
 		m.ResetActionLabel()
 		return nil
+	case sopnode.FieldActionForward:
+		m.ResetActionForward()
+		return nil
 	}
 	return fmt.Errorf("unknown SopNode field %s", name)
 }
@@ -16141,6 +16214,7 @@ type SopStageMutation struct {
 	appendaction_message  []custom_types.Action
 	action_label          *[]uint64
 	appendaction_label    []uint64
+	action_forward        **custom_types.ActionForward
 	index_sort            *int
 	addindex_sort         *int
 	clearedFields         map[string]struct{}
@@ -16817,6 +16891,55 @@ func (m *SopStageMutation) ResetActionLabel() {
 	delete(m.clearedFields, sopstage.FieldActionLabel)
 }
 
+// SetActionForward sets the "action_forward" field.
+func (m *SopStageMutation) SetActionForward(ctf *custom_types.ActionForward) {
+	m.action_forward = &ctf
+}
+
+// ActionForward returns the value of the "action_forward" field in the mutation.
+func (m *SopStageMutation) ActionForward() (r *custom_types.ActionForward, exists bool) {
+	v := m.action_forward
+	if v == nil {
+		return
+	}
+	return *v, true
+}
+
+// OldActionForward returns the old "action_forward" field's value of the SopStage entity.
+// If the SopStage object wasn't provided to the builder, the object is fetched from the database.
+// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
+func (m *SopStageMutation) OldActionForward(ctx context.Context) (v *custom_types.ActionForward, err error) {
+	if !m.op.Is(OpUpdateOne) {
+		return v, errors.New("OldActionForward is only allowed on UpdateOne operations")
+	}
+	if m.id == nil || m.oldValue == nil {
+		return v, errors.New("OldActionForward requires an ID field in the mutation")
+	}
+	oldValue, err := m.oldValue(ctx)
+	if err != nil {
+		return v, fmt.Errorf("querying old value for OldActionForward: %w", err)
+	}
+	return oldValue.ActionForward, nil
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (m *SopStageMutation) ClearActionForward() {
+	m.action_forward = nil
+	m.clearedFields[sopstage.FieldActionForward] = struct{}{}
+}
+
+// ActionForwardCleared returns if the "action_forward" field was cleared in this mutation.
+func (m *SopStageMutation) ActionForwardCleared() bool {
+	_, ok := m.clearedFields[sopstage.FieldActionForward]
+	return ok
+}
+
+// ResetActionForward resets all changes to the "action_forward" field.
+func (m *SopStageMutation) ResetActionForward() {
+	m.action_forward = nil
+	delete(m.clearedFields, sopstage.FieldActionForward)
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (m *SopStageMutation) SetIndexSort(i int) {
 	m.index_sort = &i
@@ -17069,7 +17192,7 @@ func (m *SopStageMutation) Type() string {
 // order to get all numeric fields that were incremented/decremented, call
 // AddedFields().
 func (m *SopStageMutation) Fields() []string {
-	fields := make([]string, 0, 12)
+	fields := make([]string, 0, 13)
 	if m.created_at != nil {
 		fields = append(fields, sopstage.FieldCreatedAt)
 	}
@@ -17103,6 +17226,9 @@ func (m *SopStageMutation) Fields() []string {
 	if m.action_label != nil {
 		fields = append(fields, sopstage.FieldActionLabel)
 	}
+	if m.action_forward != nil {
+		fields = append(fields, sopstage.FieldActionForward)
+	}
 	if m.index_sort != nil {
 		fields = append(fields, sopstage.FieldIndexSort)
 	}
@@ -17136,6 +17262,8 @@ func (m *SopStageMutation) Field(name string) (ent.Value, bool) {
 		return m.ActionMessage()
 	case sopstage.FieldActionLabel:
 		return m.ActionLabel()
+	case sopstage.FieldActionForward:
+		return m.ActionForward()
 	case sopstage.FieldIndexSort:
 		return m.IndexSort()
 	}
@@ -17169,6 +17297,8 @@ func (m *SopStageMutation) OldField(ctx context.Context, name string) (ent.Value
 		return m.OldActionMessage(ctx)
 	case sopstage.FieldActionLabel:
 		return m.OldActionLabel(ctx)
+	case sopstage.FieldActionForward:
+		return m.OldActionForward(ctx)
 	case sopstage.FieldIndexSort:
 		return m.OldIndexSort(ctx)
 	}
@@ -17257,6 +17387,13 @@ func (m *SopStageMutation) SetField(name string, value ent.Value) error {
 		}
 		m.SetActionLabel(v)
 		return nil
+	case sopstage.FieldActionForward:
+		v, ok := value.(*custom_types.ActionForward)
+		if !ok {
+			return fmt.Errorf("unexpected type %T for field %s", value, name)
+		}
+		m.SetActionForward(v)
+		return nil
 	case sopstage.FieldIndexSort:
 		v, ok := value.(int)
 		if !ok {
@@ -17357,6 +17494,9 @@ func (m *SopStageMutation) ClearedFields() []string {
 	if m.FieldCleared(sopstage.FieldActionLabel) {
 		fields = append(fields, sopstage.FieldActionLabel)
 	}
+	if m.FieldCleared(sopstage.FieldActionForward) {
+		fields = append(fields, sopstage.FieldActionForward)
+	}
 	if m.FieldCleared(sopstage.FieldIndexSort) {
 		fields = append(fields, sopstage.FieldIndexSort)
 	}
@@ -17386,6 +17526,9 @@ func (m *SopStageMutation) ClearField(name string) error {
 	case sopstage.FieldActionLabel:
 		m.ClearActionLabel()
 		return nil
+	case sopstage.FieldActionForward:
+		m.ClearActionForward()
+		return nil
 	case sopstage.FieldIndexSort:
 		m.ClearIndexSort()
 		return nil
@@ -17430,6 +17573,9 @@ func (m *SopStageMutation) ResetField(name string) error {
 	case sopstage.FieldActionLabel:
 		m.ResetActionLabel()
 		return nil
+	case sopstage.FieldActionForward:
+		m.ResetActionForward()
+		return nil
 	case sopstage.FieldIndexSort:
 		m.ResetIndexSort()
 		return nil

+ 1 - 1
ent/runtime/runtime.go

@@ -661,7 +661,7 @@ func init() {
 	// sopstage.DefaultConditionOperator holds the default value on creation for the condition_operator field.
 	sopstage.DefaultConditionOperator = sopstageDescConditionOperator.Default.(int)
 	// sopstageDescIndexSort is the schema descriptor for index_sort field.
-	sopstageDescIndexSort := sopstageFields[7].Descriptor()
+	sopstageDescIndexSort := sopstageFields[8].Descriptor()
 	// sopstage.DefaultIndexSort holds the default value on creation for the index_sort field.
 	sopstage.DefaultIndexSort = sopstageDescIndexSort.Default.(int)
 	soptaskMixin := schema.SopTask{}.Mixin()

+ 3 - 0
ent/schema/sop_node.go

@@ -43,6 +43,9 @@ func (SopNode) Fields() []ent.Field {
 		field.JSON("action_label", []uint64{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后需要打的标签"),
+		field.JSON("action_forward", &custom_types.ActionForward{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后转发的消息"),
 	}
 }
 

+ 3 - 0
ent/schema/sop_stage.go

@@ -40,6 +40,9 @@ func (SopStage) Fields() []ent.Field {
 		field.JSON("action_label", []uint64{}).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("命中后需要打的标签"),
+		field.JSON("action_forward", &custom_types.ActionForward{}).Optional().
+			Annotations(entsql.WithComments(true)).
+			Comment("命中后转发的消息"),
 		field.Int("index_sort").Default(1).Optional().
 			Annotations(entsql.WithComments(true)).
 			Comment("阶段顺序"),

+ 48 - 0
ent/set_not_nil.go

@@ -3368,6 +3368,30 @@ func (sn *SopNodeCreate) SetNotNilActionLabel(value []uint64) *SopNodeCreate {
 }
 
 // set field if value's pointer is not nil.
+func (sn *SopNodeUpdate) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeUpdate {
+	if value != nil {
+		return sn.SetActionForward(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeUpdateOne) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeUpdateOne {
+	if value != nil {
+		return sn.SetActionForward(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
+func (sn *SopNodeCreate) SetNotNilActionForward(value **custom_types.ActionForward) *SopNodeCreate {
+	if value != nil {
+		return sn.SetActionForward(*value)
+	}
+	return sn
+}
+
+// set field if value's pointer is not nil.
 func (ss *SopStageUpdate) SetNotNilUpdatedAt(value *time.Time) *SopStageUpdate {
 	if value != nil {
 		return ss.SetUpdatedAt(*value)
@@ -3608,6 +3632,30 @@ func (ss *SopStageCreate) SetNotNilActionLabel(value []uint64) *SopStageCreate {
 }
 
 // set field if value's pointer is not nil.
+func (ss *SopStageUpdate) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageUpdate {
+	if value != nil {
+		return ss.SetActionForward(*value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageUpdateOne) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageUpdateOne {
+	if value != nil {
+		return ss.SetActionForward(*value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
+func (ss *SopStageCreate) SetNotNilActionForward(value **custom_types.ActionForward) *SopStageCreate {
+	if value != nil {
+		return ss.SetActionForward(*value)
+	}
+	return ss
+}
+
+// set field if value's pointer is not nil.
 func (ss *SopStageUpdate) SetNotNilIndexSort(value *int) *SopStageUpdate {
 	if value != nil {
 		return ss.SetIndexSort(*value)

+ 14 - 1
ent/sopnode.go

@@ -44,6 +44,8 @@ type SopNode struct {
 	ActionMessage []custom_types.Action `json:"action_message,omitempty"`
 	// 命中后需要打的标签
 	ActionLabel []uint64 `json:"action_label,omitempty"`
+	// 命中后转发的消息
+	ActionForward *custom_types.ActionForward `json:"action_forward,omitempty"`
 	// Edges holds the relations/edges for other nodes in the graph.
 	// The values are being populated by the SopNodeQuery when eager-loading is set.
 	Edges        SopNodeEdges `json:"edges"`
@@ -86,7 +88,7 @@ func (*SopNode) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case sopnode.FieldConditionList, sopnode.FieldActionMessage, sopnode.FieldActionLabel:
+		case sopnode.FieldConditionList, sopnode.FieldActionMessage, sopnode.FieldActionLabel, sopnode.FieldActionForward:
 			values[i] = new([]byte)
 		case sopnode.FieldID, sopnode.FieldStatus, sopnode.FieldStageID, sopnode.FieldParentID, sopnode.FieldConditionType, sopnode.FieldNoReplyCondition:
 			values[i] = new(sql.NullInt64)
@@ -193,6 +195,14 @@ func (sn *SopNode) assignValues(columns []string, values []any) error {
 					return fmt.Errorf("unmarshal field action_label: %w", err)
 				}
 			}
+		case sopnode.FieldActionForward:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_forward", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &sn.ActionForward); err != nil {
+					return fmt.Errorf("unmarshal field action_forward: %w", err)
+				}
+			}
 		default:
 			sn.selectValues.Set(columns[i], values[i])
 		}
@@ -274,6 +284,9 @@ func (sn *SopNode) String() string {
 	builder.WriteString(", ")
 	builder.WriteString("action_label=")
 	builder.WriteString(fmt.Sprintf("%v", sn.ActionLabel))
+	builder.WriteString(", ")
+	builder.WriteString("action_forward=")
+	builder.WriteString(fmt.Sprintf("%v", sn.ActionForward))
 	builder.WriteByte(')')
 	return builder.String()
 }

+ 3 - 0
ent/sopnode/sopnode.go

@@ -39,6 +39,8 @@ const (
 	FieldActionMessage = "action_message"
 	// FieldActionLabel holds the string denoting the action_label field in the database.
 	FieldActionLabel = "action_label"
+	// FieldActionForward holds the string denoting the action_forward field in the database.
+	FieldActionForward = "action_forward"
 	// EdgeSopStage holds the string denoting the sop_stage edge name in mutations.
 	EdgeSopStage = "sop_stage"
 	// EdgeNodeMessages holds the string denoting the node_messages edge name in mutations.
@@ -76,6 +78,7 @@ var Columns = []string{
 	FieldNoReplyCondition,
 	FieldActionMessage,
 	FieldActionLabel,
+	FieldActionForward,
 }
 
 // ValidColumn reports if the column name is valid (part of the table columns).

+ 10 - 0
ent/sopnode/where.go

@@ -515,6 +515,16 @@ func ActionLabelNotNil() predicate.SopNode {
 	return predicate.SopNode(sql.FieldNotNull(FieldActionLabel))
 }
 
+// ActionForwardIsNil applies the IsNil predicate on the "action_forward" field.
+func ActionForwardIsNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldIsNull(FieldActionForward))
+}
+
+// ActionForwardNotNil applies the NotNil predicate on the "action_forward" field.
+func ActionForwardNotNil() predicate.SopNode {
+	return predicate.SopNode(sql.FieldNotNull(FieldActionForward))
+}
+
 // HasSopStage applies the HasEdge predicate on the "sop_stage" edge.
 func HasSopStage() predicate.SopNode {
 	return predicate.SopNode(func(s *sql.Selector) {

+ 70 - 0
ent/sopnode_create.go

@@ -153,6 +153,12 @@ func (snc *SopNodeCreate) SetActionLabel(u []uint64) *SopNodeCreate {
 	return snc
 }
 
+// SetActionForward sets the "action_forward" field.
+func (snc *SopNodeCreate) SetActionForward(ctf *custom_types.ActionForward) *SopNodeCreate {
+	snc.mutation.SetActionForward(ctf)
+	return snc
+}
+
 // SetID sets the "id" field.
 func (snc *SopNodeCreate) SetID(u uint64) *SopNodeCreate {
 	snc.mutation.SetID(u)
@@ -358,6 +364,10 @@ func (snc *SopNodeCreate) createSpec() (*SopNode, *sqlgraph.CreateSpec) {
 		_spec.SetField(sopnode.FieldActionLabel, field.TypeJSON, value)
 		_node.ActionLabel = value
 	}
+	if value, ok := snc.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+		_node.ActionForward = value
+	}
 	if nodes := snc.mutation.SopStageIDs(); len(nodes) > 0 {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.M2O,
@@ -629,6 +639,24 @@ func (u *SopNodeUpsert) ClearActionLabel() *SopNodeUpsert {
 	return u
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsert) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsert {
+	u.Set(sopnode.FieldActionForward, v)
+	return u
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsert) UpdateActionForward() *SopNodeUpsert {
+	u.SetExcluded(sopnode.FieldActionForward)
+	return u
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsert) ClearActionForward() *SopNodeUpsert {
+	u.SetNull(sopnode.FieldActionForward)
+	return u
+}
+
 // UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field.
 // Using this option is equivalent to using:
 //
@@ -897,6 +925,27 @@ func (u *SopNodeUpsertOne) ClearActionLabel() *SopNodeUpsertOne {
 	})
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsertOne) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsertOne) UpdateActionForward() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsertOne) ClearActionForward() *SopNodeUpsertOne {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionForward()
+	})
+}
+
 // Exec executes the query.
 func (u *SopNodeUpsertOne) Exec(ctx context.Context) error {
 	if len(u.create.conflict) == 0 {
@@ -1331,6 +1380,27 @@ func (u *SopNodeUpsertBulk) ClearActionLabel() *SopNodeUpsertBulk {
 	})
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopNodeUpsertBulk) SetActionForward(v *custom_types.ActionForward) *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopNodeUpsertBulk) UpdateActionForward() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopNodeUpsertBulk) ClearActionForward() *SopNodeUpsertBulk {
+	return u.Update(func(s *SopNodeUpsert) {
+		s.ClearActionForward()
+	})
+}
+
 // Exec executes the query.
 func (u *SopNodeUpsertBulk) Exec(ctx context.Context) error {
 	if u.create.err != nil {

+ 36 - 0
ent/sopnode_update.go

@@ -230,6 +230,18 @@ func (snu *SopNodeUpdate) ClearActionLabel() *SopNodeUpdate {
 	return snu
 }
 
+// SetActionForward sets the "action_forward" field.
+func (snu *SopNodeUpdate) SetActionForward(ctf *custom_types.ActionForward) *SopNodeUpdate {
+	snu.mutation.SetActionForward(ctf)
+	return snu
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (snu *SopNodeUpdate) ClearActionForward() *SopNodeUpdate {
+	snu.mutation.ClearActionForward()
+	return snu
+}
+
 // SetSopStageID sets the "sop_stage" edge to the SopStage entity by ID.
 func (snu *SopNodeUpdate) SetSopStageID(id uint64) *SopNodeUpdate {
 	snu.mutation.SetSopStageID(id)
@@ -422,6 +434,12 @@ func (snu *SopNodeUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if snu.mutation.ActionLabelCleared() {
 		_spec.ClearField(sopnode.FieldActionLabel, field.TypeJSON)
 	}
+	if value, ok := snu.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+	}
+	if snu.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopnode.FieldActionForward, field.TypeJSON)
+	}
 	if snu.mutation.SopStageCleared() {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.M2O,
@@ -714,6 +732,18 @@ func (snuo *SopNodeUpdateOne) ClearActionLabel() *SopNodeUpdateOne {
 	return snuo
 }
 
+// SetActionForward sets the "action_forward" field.
+func (snuo *SopNodeUpdateOne) SetActionForward(ctf *custom_types.ActionForward) *SopNodeUpdateOne {
+	snuo.mutation.SetActionForward(ctf)
+	return snuo
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (snuo *SopNodeUpdateOne) ClearActionForward() *SopNodeUpdateOne {
+	snuo.mutation.ClearActionForward()
+	return snuo
+}
+
 // SetSopStageID sets the "sop_stage" edge to the SopStage entity by ID.
 func (snuo *SopNodeUpdateOne) SetSopStageID(id uint64) *SopNodeUpdateOne {
 	snuo.mutation.SetSopStageID(id)
@@ -936,6 +966,12 @@ func (snuo *SopNodeUpdateOne) sqlSave(ctx context.Context) (_node *SopNode, err
 	if snuo.mutation.ActionLabelCleared() {
 		_spec.ClearField(sopnode.FieldActionLabel, field.TypeJSON)
 	}
+	if value, ok := snuo.mutation.ActionForward(); ok {
+		_spec.SetField(sopnode.FieldActionForward, field.TypeJSON, value)
+	}
+	if snuo.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopnode.FieldActionForward, field.TypeJSON)
+	}
 	if snuo.mutation.SopStageCleared() {
 		edge := &sqlgraph.EdgeSpec{
 			Rel:     sqlgraph.M2O,

+ 14 - 1
ent/sopstage.go

@@ -42,6 +42,8 @@ type SopStage struct {
 	ActionMessage []custom_types.Action `json:"action_message,omitempty"`
 	// 命中后需要打的标签
 	ActionLabel []uint64 `json:"action_label,omitempty"`
+	// 命中后转发的消息
+	ActionForward *custom_types.ActionForward `json:"action_forward,omitempty"`
 	// 阶段顺序
 	IndexSort int `json:"index_sort,omitempty"`
 	// Edges holds the relations/edges for other nodes in the graph.
@@ -97,7 +99,7 @@ func (*SopStage) scanValues(columns []string) ([]any, error) {
 	values := make([]any, len(columns))
 	for i := range columns {
 		switch columns[i] {
-		case sopstage.FieldConditionList, sopstage.FieldActionMessage, sopstage.FieldActionLabel:
+		case sopstage.FieldConditionList, sopstage.FieldActionMessage, sopstage.FieldActionLabel, sopstage.FieldActionForward:
 			values[i] = new([]byte)
 		case sopstage.FieldID, sopstage.FieldStatus, sopstage.FieldTaskID, sopstage.FieldConditionType, sopstage.FieldConditionOperator, sopstage.FieldIndexSort:
 			values[i] = new(sql.NullInt64)
@@ -198,6 +200,14 @@ func (ss *SopStage) assignValues(columns []string, values []any) error {
 					return fmt.Errorf("unmarshal field action_label: %w", err)
 				}
 			}
+		case sopstage.FieldActionForward:
+			if value, ok := values[i].(*[]byte); !ok {
+				return fmt.Errorf("unexpected type %T for field action_forward", values[i])
+			} else if value != nil && len(*value) > 0 {
+				if err := json.Unmarshal(*value, &ss.ActionForward); err != nil {
+					return fmt.Errorf("unmarshal field action_forward: %w", err)
+				}
+			}
 		case sopstage.FieldIndexSort:
 			if value, ok := values[i].(*sql.NullInt64); !ok {
 				return fmt.Errorf("unexpected type %T for field index_sort", values[i])
@@ -288,6 +298,9 @@ func (ss *SopStage) String() string {
 	builder.WriteString("action_label=")
 	builder.WriteString(fmt.Sprintf("%v", ss.ActionLabel))
 	builder.WriteString(", ")
+	builder.WriteString("action_forward=")
+	builder.WriteString(fmt.Sprintf("%v", ss.ActionForward))
+	builder.WriteString(", ")
 	builder.WriteString("index_sort=")
 	builder.WriteString(fmt.Sprintf("%v", ss.IndexSort))
 	builder.WriteByte(')')

+ 3 - 0
ent/sopstage/sopstage.go

@@ -37,6 +37,8 @@ const (
 	FieldActionMessage = "action_message"
 	// FieldActionLabel holds the string denoting the action_label field in the database.
 	FieldActionLabel = "action_label"
+	// FieldActionForward holds the string denoting the action_forward field in the database.
+	FieldActionForward = "action_forward"
 	// FieldIndexSort holds the string denoting the index_sort field in the database.
 	FieldIndexSort = "index_sort"
 	// EdgeSopTask holds the string denoting the sop_task edge name in mutations.
@@ -84,6 +86,7 @@ var Columns = []string{
 	FieldConditionList,
 	FieldActionMessage,
 	FieldActionLabel,
+	FieldActionForward,
 	FieldIndexSort,
 }
 

+ 10 - 0
ent/sopstage/where.go

@@ -465,6 +465,16 @@ func ActionLabelNotNil() predicate.SopStage {
 	return predicate.SopStage(sql.FieldNotNull(FieldActionLabel))
 }
 
+// ActionForwardIsNil applies the IsNil predicate on the "action_forward" field.
+func ActionForwardIsNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldIsNull(FieldActionForward))
+}
+
+// ActionForwardNotNil applies the NotNil predicate on the "action_forward" field.
+func ActionForwardNotNil() predicate.SopStage {
+	return predicate.SopStage(sql.FieldNotNull(FieldActionForward))
+}
+
 // IndexSortEQ applies the EQ predicate on the "index_sort" field.
 func IndexSortEQ(v int) predicate.SopStage {
 	return predicate.SopStage(sql.FieldEQ(FieldIndexSort, v))

+ 70 - 0
ent/sopstage_create.go

@@ -148,6 +148,12 @@ func (ssc *SopStageCreate) SetActionLabel(u []uint64) *SopStageCreate {
 	return ssc
 }
 
+// SetActionForward sets the "action_forward" field.
+func (ssc *SopStageCreate) SetActionForward(ctf *custom_types.ActionForward) *SopStageCreate {
+	ssc.mutation.SetActionForward(ctf)
+	return ssc
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (ssc *SopStageCreate) SetIndexSort(i int) *SopStageCreate {
 	ssc.mutation.SetIndexSort(i)
@@ -382,6 +388,10 @@ func (ssc *SopStageCreate) createSpec() (*SopStage, *sqlgraph.CreateSpec) {
 		_spec.SetField(sopstage.FieldActionLabel, field.TypeJSON, value)
 		_node.ActionLabel = value
 	}
+	if value, ok := ssc.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+		_node.ActionForward = value
+	}
 	if value, ok := ssc.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)
 		_node.IndexSort = value
@@ -649,6 +659,24 @@ func (u *SopStageUpsert) ClearActionLabel() *SopStageUpsert {
 	return u
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsert) SetActionForward(v *custom_types.ActionForward) *SopStageUpsert {
+	u.Set(sopstage.FieldActionForward, v)
+	return u
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsert) UpdateActionForward() *SopStageUpsert {
+	u.SetExcluded(sopstage.FieldActionForward)
+	return u
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsert) ClearActionForward() *SopStageUpsert {
+	u.SetNull(sopstage.FieldActionForward)
+	return u
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (u *SopStageUpsert) SetIndexSort(v int) *SopStageUpsert {
 	u.Set(sopstage.FieldIndexSort, v)
@@ -913,6 +941,27 @@ func (u *SopStageUpsertOne) ClearActionLabel() *SopStageUpsertOne {
 	})
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsertOne) SetActionForward(v *custom_types.ActionForward) *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsertOne) UpdateActionForward() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsertOne) ClearActionForward() *SopStageUpsertOne {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionForward()
+	})
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (u *SopStageUpsertOne) SetIndexSort(v int) *SopStageUpsertOne {
 	return u.Update(func(s *SopStageUpsert) {
@@ -1347,6 +1396,27 @@ func (u *SopStageUpsertBulk) ClearActionLabel() *SopStageUpsertBulk {
 	})
 }
 
+// SetActionForward sets the "action_forward" field.
+func (u *SopStageUpsertBulk) SetActionForward(v *custom_types.ActionForward) *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.SetActionForward(v)
+	})
+}
+
+// UpdateActionForward sets the "action_forward" field to the value that was provided on create.
+func (u *SopStageUpsertBulk) UpdateActionForward() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.UpdateActionForward()
+	})
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (u *SopStageUpsertBulk) ClearActionForward() *SopStageUpsertBulk {
+	return u.Update(func(s *SopStageUpsert) {
+		s.ClearActionForward()
+	})
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (u *SopStageUpsertBulk) SetIndexSort(v int) *SopStageUpsertBulk {
 	return u.Update(func(s *SopStageUpsert) {

+ 36 - 0
ent/sopstage_update.go

@@ -204,6 +204,18 @@ func (ssu *SopStageUpdate) ClearActionLabel() *SopStageUpdate {
 	return ssu
 }
 
+// SetActionForward sets the "action_forward" field.
+func (ssu *SopStageUpdate) SetActionForward(ctf *custom_types.ActionForward) *SopStageUpdate {
+	ssu.mutation.SetActionForward(ctf)
+	return ssu
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (ssu *SopStageUpdate) ClearActionForward() *SopStageUpdate {
+	ssu.mutation.ClearActionForward()
+	return ssu
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (ssu *SopStageUpdate) SetIndexSort(i int) *SopStageUpdate {
 	ssu.mutation.ResetIndexSort()
@@ -450,6 +462,12 @@ func (ssu *SopStageUpdate) sqlSave(ctx context.Context) (n int, err error) {
 	if ssu.mutation.ActionLabelCleared() {
 		_spec.ClearField(sopstage.FieldActionLabel, field.TypeJSON)
 	}
+	if value, ok := ssu.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+	}
+	if ssu.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopstage.FieldActionForward, field.TypeJSON)
+	}
 	if value, ok := ssu.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)
 	}
@@ -769,6 +787,18 @@ func (ssuo *SopStageUpdateOne) ClearActionLabel() *SopStageUpdateOne {
 	return ssuo
 }
 
+// SetActionForward sets the "action_forward" field.
+func (ssuo *SopStageUpdateOne) SetActionForward(ctf *custom_types.ActionForward) *SopStageUpdateOne {
+	ssuo.mutation.SetActionForward(ctf)
+	return ssuo
+}
+
+// ClearActionForward clears the value of the "action_forward" field.
+func (ssuo *SopStageUpdateOne) ClearActionForward() *SopStageUpdateOne {
+	ssuo.mutation.ClearActionForward()
+	return ssuo
+}
+
 // SetIndexSort sets the "index_sort" field.
 func (ssuo *SopStageUpdateOne) SetIndexSort(i int) *SopStageUpdateOne {
 	ssuo.mutation.ResetIndexSort()
@@ -1045,6 +1075,12 @@ func (ssuo *SopStageUpdateOne) sqlSave(ctx context.Context) (_node *SopStage, er
 	if ssuo.mutation.ActionLabelCleared() {
 		_spec.ClearField(sopstage.FieldActionLabel, field.TypeJSON)
 	}
+	if value, ok := ssuo.mutation.ActionForward(); ok {
+		_spec.SetField(sopstage.FieldActionForward, field.TypeJSON, value)
+	}
+	if ssuo.mutation.ActionForwardCleared() {
+		_spec.ClearField(sopstage.FieldActionForward, field.TypeJSON)
+	}
 	if value, ok := ssuo.mutation.IndexSort(); ok {
 		_spec.SetField(sopstage.FieldIndexSort, field.TypeInt, value)
 	}

+ 5 - 0
internal/handler/routes.go

@@ -466,6 +466,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
 					Path:    "/sop_task/stop",
 					Handler: sop_task.SopTaskStopHandler(serverCtx),
 				},
+				{
+					Method:  http.MethodPost,
+					Path:    "/sop_task/copy",
+					Handler: sop_task.SopTaskCopyHandler(serverCtx),
+				},
 			}...,
 		),
 		rest.WithJwt(serverCtx.Config.Auth.AccessSecret),

+ 44 - 0
internal/handler/sop_task/sop_task_copy_handler.go

@@ -0,0 +1,44 @@
+package sop_task
+
+import (
+	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/httpx"
+
+	"wechat-api/internal/logic/sop_task"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+)
+
+// swagger:route post /sop_task/copy sop_task SopTaskCopy
+//
+// task start | SopTask 复制
+//
+// task start | SopTask 复制
+//
+// Parameters:
+//  + name: body
+//    require: true
+//    in: body
+//    type: CopyReq
+//
+// Responses:
+//  200: BaseMsgResp
+
+func SopTaskCopyHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		var req types.CopyReq
+		if err := httpx.Parse(r, &req, true); err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+			return
+		}
+
+		l := sop_task.NewSopTaskCopyLogic(r.Context(), svcCtx)
+		resp, err := l.SopTaskCopy(&req)
+		if err != nil {
+			httpx.ErrorCtx(r.Context(), w, err)
+		} else {
+			httpx.OkJsonCtx(r.Context(), w, resp)
+		}
+	}
+}

+ 37 - 3
internal/logic/sop_node/create_sop_node_logic.go

@@ -64,7 +64,36 @@ func (l *CreateSopNodeLogic) CreateSopNode(req *types.SopNodeInfo) (*types.SopNo
 		}
 	}
 
-	newNode, err := l.svcCtx.DB.SopNode.Create().
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopNode.Create().
 		SetNotNilStatus(req.Status).
 		SetNotNilStageID(req.StageId).
 		SetNotNilParentID(req.ParentId).
@@ -73,8 +102,13 @@ func (l *CreateSopNodeLogic) CreateSopNode(req *types.SopNodeInfo) (*types.SopNo
 		SetNotNilConditionList(req.ConditionList).
 		SetNotNilNoReplyCondition(req.NoReplyCondition).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		Save(l.ctx)
+		SetNotNilActionLabel(req.ActionLabel)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	}
+
+	newNode, err := createStmt.Save(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 19 - 0
internal/logic/sop_node/get_sop_node_by_id_logic.go

@@ -44,6 +44,24 @@ func (l *GetSopNodeByIdLogic) GetSopNodeById(req *types.IDReq) (*types.SopNodeIn
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopNodeInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -64,6 +82,7 @@ func (l *GetSopNodeByIdLogic) GetSopNodeById(req *types.IDReq) (*types.SopNodeIn
 			NoReplyCondition: &data.NoReplyCondition,
 			ActionMessage:    actionMessage,
 			ActionLabel:      data.ActionLabel,
+			ActionForward:    actionForward,
 		},
 	}, nil
 }

+ 19 - 0
internal/logic/sop_node/get_sop_node_detail_logic.go

@@ -42,6 +42,24 @@ func (l *GetSopNodeDetailLogic) GetSopNodeDetail(req *types.IDReq) (resp *types.
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopNodeInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -62,6 +80,7 @@ func (l *GetSopNodeDetailLogic) GetSopNodeDetail(req *types.IDReq) (resp *types.
 			NoReplyCondition: &data.NoReplyCondition,
 			ActionMessage:    actionMessage,
 			ActionLabel:      data.ActionLabel,
+			ActionForward:    actionForward,
 		},
 	}, nil
 }

+ 19 - 0
internal/logic/sop_node/get_sop_node_list_logic.go

@@ -68,6 +68,24 @@ func GetSopNodeListByParentId(parentId uint64, nodeList []*ent.SopNode) ([]*type
 				}
 			}
 
+			var actionForward *types.ActionForward
+			if node.ActionForward != nil {
+				actionForward = &types.ActionForward{}
+				actionForward.Wxid = node.ActionForward.Wxid
+				if len(node.ActionForward.Action) > 0 {
+					forwardAction := make([]types.Action, len(node.ActionForward.Action))
+					for i, condition := range node.ActionForward.Action {
+						forwardAction[i] = types.Action{
+							Type:    condition.Type,
+							Content: condition.Content,
+						}
+					}
+					actionForward.Action = forwardAction
+				}
+			} else {
+				actionForward = nil
+			}
+
 			var childNode *types.SopChildNodeInfo
 			if len(childNodeList) > 0 {
 				childNode = &types.SopChildNodeInfo{
@@ -91,6 +109,7 @@ func GetSopNodeListByParentId(parentId uint64, nodeList []*ent.SopNode) ([]*type
 					NoReplyCondition: &node.NoReplyCondition,
 					ActionMessage:    actionMessage,
 					ActionLabel:      node.ActionLabel,
+					ActionForward:    actionForward,
 				},
 			}
 			result = append(result, &child)

+ 39 - 3
internal/logic/sop_node/update_sop_node_logic.go

@@ -67,7 +67,36 @@ func (l *UpdateSopNodeLogic) UpdateSopNode(req *types.SopNodeInfo) (*types.BaseM
 		}
 	}
 
-	err = l.svcCtx.DB.SopNode.UpdateOneID(*req.Id).
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopNode.UpdateOneID(*req.Id).
 		SetNotNilStatus(req.Status).
 		SetNotNilStageID(req.StageId).
 		SetNotNilParentID(req.ParentId).
@@ -76,8 +105,15 @@ func (l *UpdateSopNodeLogic) UpdateSopNode(req *types.SopNodeInfo) (*types.BaseM
 		SetNotNilConditionList(req.ConditionList).
 		SetNotNilNoReplyCondition(req.NoReplyCondition).
 		SetNotNilActionMessage(actionMessage).
-		SetNotNilActionLabel(req.ActionLabel).
-		Exec(l.ctx)
+		SetNotNilActionLabel(req.ActionLabel)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	} else {
+		createStmt.ClearActionForward()
+	}
+
+	err = createStmt.Exec(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 35 - 3
internal/logic/sop_stage/create_sop_stage_logic.go

@@ -79,7 +79,36 @@ func (l *CreateSopStageLogic) CreateSopStage(req *types.SopStageInfo) (*types.So
 		}
 	}
 
-	newStage, err := l.svcCtx.DB.SopStage.Create().
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopStage.Create().
 		SetNotNilStatus(req.Status).
 		SetNotNilTaskID(req.TaskId).
 		SetNotNilName(req.Name).
@@ -88,8 +117,11 @@ func (l *CreateSopStageLogic) CreateSopStage(req *types.SopStageInfo) (*types.So
 		SetNotNilConditionList(conditionList).
 		SetNotNilActionMessage(actionMessage).
 		SetNotNilActionLabel(req.ActionLabel).
-		SetNotNilIndexSort(&count).
-		Save(l.ctx)
+		SetNotNilIndexSort(&count)
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	}
+	newStage, err := createStmt.Save(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 19 - 0
internal/logic/sop_stage/get_sop_stage_by_id_logic.go

@@ -58,6 +58,24 @@ func (l *GetSopStageByIdLogic) GetSopStageById(req *types.IDReq) (*types.SopStag
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	var nodeList []types.SopNodeInfo
 	if data.Edges.StageNodes != nil {
 		nodeList = make([]types.SopNodeInfo, 0)
@@ -93,6 +111,7 @@ func (l *GetSopStageByIdLogic) GetSopStageById(req *types.IDReq) (*types.SopStag
 			ConditionList:     conditionList,
 			ActionMessage:     actionMessage,
 			ActionLabel:       data.ActionLabel,
+			ActionForward:     actionForward,
 			IndexSort:         &data.IndexSort,
 			NodeList:          nodeList,
 		},

+ 19 - 0
internal/logic/sop_stage/get_sop_stage_detail_logic.go

@@ -57,6 +57,24 @@ func (l *GetSopStageDetailLogic) GetSopStageDetail(req *types.IDReq) (resp *type
 		}
 	}
 
+	var actionForward *types.ActionForward
+	if data.ActionForward != nil {
+		actionForward = &types.ActionForward{}
+		actionForward.Wxid = data.ActionForward.Wxid
+		if len(data.ActionForward.Action) > 0 {
+			forwardAction := make([]types.Action, len(data.ActionForward.Action))
+			for i, condition := range data.ActionForward.Action {
+				forwardAction[i] = types.Action{
+					Type:    condition.Type,
+					Content: condition.Content,
+				}
+			}
+			actionForward.Action = forwardAction
+		}
+	} else {
+		actionForward = nil
+	}
+
 	return &types.SopStageInfoResp{
 		BaseDataInfo: types.BaseDataInfo{
 			Code: 0,
@@ -76,6 +94,7 @@ func (l *GetSopStageDetailLogic) GetSopStageDetail(req *types.IDReq) (resp *type
 			ConditionList:     conditionList,
 			ActionMessage:     actionMessage,
 			ActionLabel:       data.ActionLabel,
+			ActionForward:     actionForward,
 			IndexSort:         &data.IndexSort,
 		},
 	}, nil

+ 19 - 0
internal/logic/sop_stage/get_sop_stage_list_logic.go

@@ -69,6 +69,24 @@ func (l *GetSopStageListLogic) GetSopStageList(req *types.SopStageListReq) (*typ
 			}
 		}
 
+		var actionForward *types.ActionForward
+		if v.ActionForward != nil {
+			actionForward = &types.ActionForward{}
+			actionForward.Wxid = v.ActionForward.Wxid
+			if len(v.ActionForward.Action) > 0 {
+				forwardAction := make([]types.Action, len(v.ActionForward.Action))
+				for i, condition := range v.ActionForward.Action {
+					forwardAction[i] = types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				}
+				actionForward.Action = forwardAction
+			}
+		} else {
+			actionForward = nil
+		}
+
 		resp.Data.Data = append(resp.Data.Data,
 			types.SopStageInfo{
 				BaseIDInfo: types.BaseIDInfo{
@@ -84,6 +102,7 @@ func (l *GetSopStageListLogic) GetSopStageList(req *types.SopStageListReq) (*typ
 				ConditionList:     conditionList,
 				ActionMessage:     actionMessage,
 				ActionLabel:       v.ActionLabel,
+				ActionForward:     actionForward,
 				IndexSort:         &v.IndexSort,
 			})
 	}

+ 39 - 3
internal/logic/sop_stage/update_sop_stage_logic.go

@@ -74,7 +74,36 @@ func (l *UpdateSopStageLogic) UpdateSopStage(req *types.SopStageInfo) (*types.Ba
 		}
 	}
 
-	err = l.svcCtx.DB.SopStage.UpdateOneID(*req.Id).
+	var forward *custom_types.ActionForward
+	if req.ActionForward != nil {
+		forward = &custom_types.ActionForward{}
+		forward.Wxid = req.ActionForward.Wxid
+		var forwardAction []custom_types.Action
+		if len(req.ActionForward.Action) > 0 {
+			forwardAction = make([]custom_types.Action, len(req.ActionForward.Action))
+			for i, condition := range req.ActionForward.Action {
+				if condition.Type == 1 {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+					}
+				} else {
+					forwardAction[i] = custom_types.Action{
+						Type:    condition.Type,
+						Content: condition.Content,
+						Meta: &custom_types.Meta{
+							Filename: condition.Meta.Filename,
+						},
+					}
+				}
+			}
+		}
+		forward.Action = forwardAction
+	} else {
+		forward = nil
+	}
+
+	createStmt := l.svcCtx.DB.SopStage.UpdateOneID(*req.Id).
 		SetNotNilStatus(req.Status).
 		SetNotNilTaskID(req.TaskId).
 		SetNotNilName(req.Name).
@@ -83,8 +112,15 @@ func (l *UpdateSopStageLogic) UpdateSopStage(req *types.SopStageInfo) (*types.Ba
 		SetNotNilConditionList(conditionList).
 		SetNotNilActionMessage(actionMessage).
 		SetNotNilActionLabel(req.ActionLabel).
-		SetNotNilIndexSort(req.IndexSort).
-		Exec(l.ctx)
+		SetNotNilIndexSort(req.IndexSort)
+
+	if forward != nil {
+		createStmt.SetActionForward(forward)
+	} else {
+		createStmt.ClearActionForward()
+	}
+
+	err = createStmt.Exec(l.ctx)
 
 	if err != nil {
 		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)

+ 7 - 1
internal/logic/sop_task/get_sop_task_list_logic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"wechat-api/ent/predicate"
 	"wechat-api/ent/soptask"
+	"wechat-api/ent/wx"
 	"wechat-api/internal/svc"
 	"wechat-api/internal/types"
 	"wechat-api/internal/utils/dberrorhandler"
@@ -49,6 +50,11 @@ func (l *GetSopTaskListLogic) GetSopTaskList(req *types.SopTaskListReq) (*types.
 	resp.Data.Total = data.PageDetails.Total
 
 	for _, v := range data.List {
+		wxInfos, _ := l.svcCtx.DB.Wx.Query().Where(wx.WxidIn(v.BotWxidList...)).All(l.ctx)
+		var botWxidList []string
+		for _, b := range wxInfos {
+			botWxidList = append(botWxidList, b.Nickname)
+		}
 		resp.Data.Data = append(resp.Data.Data,
 			types.SopTaskInfo{
 				BaseIDInfo: types.BaseIDInfo{
@@ -58,7 +64,7 @@ func (l *GetSopTaskListLogic) GetSopTaskList(req *types.SopTaskListReq) (*types.
 				},
 				Status:        &v.Status,
 				Name:          &v.Name,
-				BotWxidList:   v.BotWxidList,
+				BotWxidList:   botWxidList,
 				Type:          &v.Type,
 				PlanStartTime: pointy.GetUnixMilliPointer(v.PlanStartTime.UnixMilli()),
 				PlanEndTime:   pointy.GetUnixMilliPointer(v.PlanEndTime.UnixMilli()),

+ 126 - 0
internal/logic/sop_task/sop_task_copy_logic.go

@@ -0,0 +1,126 @@
+package sop_task
+
+import (
+	"context"
+	"github.com/suyuan32/simple-admin-common/msg/errormsg"
+	"wechat-api/ent"
+	"wechat-api/ent/sopnode"
+	"wechat-api/ent/soptask"
+	"wechat-api/internal/svc"
+	"wechat-api/internal/types"
+	"wechat-api/internal/utils/dberrorhandler"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SopTaskCopyLogic struct {
+	logx.Logger
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+}
+
+func NewSopTaskCopyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SopTaskCopyLogic {
+	return &SopTaskCopyLogic{
+		Logger: logx.WithContext(ctx),
+		ctx:    ctx,
+		svcCtx: svcCtx}
+}
+
+func (l *SopTaskCopyLogic) SopTaskCopy(req *types.CopyReq) (resp *types.BaseMsgResp, err error) {
+	isAdmin := l.ctx.Value("isAdmin").(bool)
+	organizationId := l.ctx.Value("organizationId").(uint64)
+
+	var targetOrganizationId uint64
+	if isAdmin {
+		if req.OrganizationId == 0 {
+			targetOrganizationId = organizationId
+		} else {
+			targetOrganizationId = req.OrganizationId
+		}
+	} else {
+		targetOrganizationId = organizationId
+	}
+
+	data, err := l.svcCtx.DB.SopTask.Query().
+		Where(soptask.ID(req.Id), soptask.OrganizationIDEQ(organizationId)).
+		WithTaskStages().
+		Only(l.ctx)
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	// 开始事务
+	tx, err := l.svcCtx.DB.Tx(context.Background())
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	newTask, err := tx.SopTask.Create().
+		SetName(*req.Name).
+		SetType(data.Type).
+		SetOrganizationID(targetOrganizationId).
+		Save(l.ctx)
+	if err != nil {
+		_ = tx.Rollback()
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	for _, s := range data.Edges.TaskStages {
+		newStage, err := tx.SopStage.Create().
+			SetTaskID(newTask.ID).
+			SetName(s.Name).
+			SetConditionType(s.ConditionType).
+			SetConditionOperator(s.ConditionOperator).
+			SetConditionList(s.ConditionList).
+			SetNotNilActionMessage(s.ActionMessage).
+			SetNotNilActionLabel(s.ActionLabel).
+			SetIndexSort(s.IndexSort).
+			Save(l.ctx)
+		if err != nil {
+			_ = tx.Rollback()
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+		err = CreateNodes(tx, s.ID, 0, newStage.ID, 0, l.ctx, l.Logger)
+		if err != nil {
+			_ = tx.Rollback()
+			return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+		}
+	}
+
+	// 所有操作成功,提交事务
+	err = tx.Commit()
+	if err != nil {
+		return nil, dberrorhandler.DefaultEntError(l.Logger, err, req)
+	}
+
+	return &types.BaseMsgResp{Msg: errormsg.CreateSuccess}, nil
+}
+
+func CreateNodes(tx *ent.Tx, stageId uint64, parentId uint64, newStageId uint64, newParentId uint64, ctx context.Context, logger logx.Logger) (err error) {
+	data, err := tx.SopNode.Query().
+		Where(sopnode.StageID(stageId), sopnode.ParentID(parentId)).All(ctx)
+	if err != nil {
+		return nil
+	}
+
+	for _, n := range data {
+		newNode, err := tx.SopNode.Create().
+			SetStageID(newStageId).
+			SetParentID(newParentId).
+			SetName(n.Name).
+			SetConditionType(n.ConditionType).
+			SetConditionList(n.ConditionList).
+			SetNotNilNoReplyCondition(&n.NoReplyCondition).
+			SetNotNilActionMessage(n.ActionMessage).
+			SetNotNilActionLabel(n.ActionLabel).
+			Save(ctx)
+		if err != nil {
+			return err
+		}
+		err = CreateNodes(tx, stageId, n.ID, newStageId, newNode.ID, ctx, logger)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 19 - 0
internal/types/types.go

@@ -235,6 +235,11 @@ type Meta struct {
 	Filename string `json:"filename,optional"`
 }
 
+type ActionForward struct {
+	Wxid   string   `json:"wxid"`
+	Action []Action `json:"action"`
+}
+
 // The response data of server information | Server信息
 // swagger:model ServerInfo
 type ServerInfo struct {
@@ -1143,6 +1148,16 @@ type SopTaskCreateResp struct {
 	Data uint64 `json:"data"`
 }
 
+// swagger:model CopyReq
+type CopyReq struct {
+	// SopTask id | SopTask id
+	Id uint64 `json:"id"`
+	// SOP 任务名称
+	Name *string `json:"name,optional"`
+	// Organization id | Organization id
+	OrganizationId uint64 `json:"organizationId,optional"`
+}
+
 // The response data of sop stage information | SopStage信息
 // swagger:model SopStageInfo
 type SopStageInfo struct {
@@ -1163,6 +1178,8 @@ type SopStageInfo struct {
 	ActionMessage []Action `json:"actionMessage,optional"`
 	// 命中后需要打的标签
 	ActionLabel []uint64 `json:"actionLabel,optional"`
+	// 命中后转发的消息内容
+	ActionForward *ActionForward `json:"actionForward,optional"`
 	// 阶段顺序
 	IndexSort *int `json:"indexSort,optional"`
 	// sop 任务信息
@@ -1217,6 +1234,8 @@ type SopNodeInfo struct {
 	ActionMessage []Action `json:"actionMessage,optional"`
 	// 命中后需要打的标签
 	ActionLabel []uint64 `json:"actionLabel,optional"`
+	// 命中后转发的消息内容
+	ActionForward *ActionForward `json:"actionForward,optional"`
 	// 阶段信息
 	StageInfo *SopStageInfo `json:"stageInfo,optional"`
 }