package orbit import ( "context" "encoding/json" "errors" "time" ) // WorkflowEventSummary 只表示已保存的事件,不把当前状态或当前激活反填到历史。 type WorkflowEventSummary struct { ID string `json:"id"` // ID 原不可变状态事件标识。 WorkflowID string `json:"workflow_id"` // WorkflowID 已核验授权的流程归属。 AggregateKind string `json:"aggregate_kind"` // AggregateKind workflow、workflow_step或workflow_signal。 AggregateID string `json:"aggregate_id"` // AggregateID 产生事件的实例、步骤或信号身份。 AggregateRevision uint64 `json:"aggregate_revision,string"` // AggregateRevision 来源资源版本,各资源独立,不是全局序号。 Type string `json:"type"` // Type 原始事件类型,不按当前状态改写。 StateScope string `json:"state_scope"` // StateScope WORKFLOW、STEP或SIGNAL,说明State记录的是哪类对象。 State string `json:"state"` // State 该事件保存的状态,不代表对象当前状态。 StepKey string `json:"step_key,omitempty"` // StepKey 步骤事件的固定定义名称;其他事件省略。 OccurredAt time.Time `json:"occurred_at"` // OccurredAt 数据库事件记录时间,UTC微秒精度,不是外部业务发生时间或提交水位。 } type WorkflowEventPage struct { WorkflowID string `json:"workflow_id"` // WorkflowID 本页授权的实例身份。 Revision uint64 `json:"revision,string"` // Revision 本页读取时实例版本,不能作为信号或步骤事件的消费水位。 Items []WorkflowEventSummary `json:"items"` // Items 按记录时间和ID倒序的有界摘要,空列表为[]。 NextCursor string `json:"next_cursor,omitempty"` // NextCursor 绑定实例与事件查询种类,不承诺跨页快照。 } type WorkflowEventDetail struct { WorkflowEventSummary Payload json.RawMessage `json:"payload"` // Payload 原不可变事件元数据,最多64KiB;嵌套数字保留原文本,输入/结果另走资源详情。 } // ListWorkflowEvents 读取实例、步骤和信号的不可变状态事件,需要workflows:audit:read。 func (c *Client) ListWorkflowEvents(ctx context.Context, id string, in HistoryPageRequest) (WorkflowEventPage, error) { var out WorkflowEventPage if !validID(id) { return out, errors.New("invalid workflow id") } query, err := historyPageQuery(in) if err != nil { return out, err } if err = c.call(ctx, "GET", "/v1/workflows/"+id+"/events"+query, "", nil, &out); err != nil { return WorkflowEventPage{}, err } if out.WorkflowID != id || out.Revision == 0 || out.Items == nil || !historyResponseSize(len(out.Items), in) { return WorkflowEventPage{}, errors.New("invalid workflow event page") } for i, e := range out.Items { if !validWorkflowEvent(e, id) || i > 0 && (e.OccurredAt.After(out.Items[i-1].OccurredAt) || e.OccurredAt.Equal(out.Items[i-1].OccurredAt) && e.ID >= out.Items[i-1].ID) { return WorkflowEventPage{}, errors.New("invalid workflow event summary or ordering") } } return out, nil } func validWorkflowEvent(e WorkflowEventSummary, id string) bool { if e.WorkflowID != id || !validID(e.ID) || !validID(e.AggregateID) || e.AggregateRevision == 0 || e.Type == "" || e.State == "" || e.OccurredAt.IsZero() { return false } switch e.AggregateKind { case "workflow": if e.AggregateID != id { return false } case "workflow_step": if !workflowVersionName(e.StepKey) { return false } case "workflow_signal": default: return false } return e.StateScope == "WORKFLOW" || e.StateScope == "STEP" || e.StateScope == "SIGNAL" } func (c *Client) GetWorkflowEvent(ctx context.Context, id, event string) (WorkflowEventDetail, error) { var out WorkflowEventDetail if !validID(id) || !validID(event) { return out, errors.New("invalid workflow event identity") } if err := c.call(ctx, "GET", "/v1/workflows/"+id+"/events/"+event, "", nil, &out); err != nil { return WorkflowEventDetail{}, err } if out.ID != event || !validWorkflowEvent(out.WorkflowEventSummary, id) || len(out.Payload) == 0 || len(out.Payload) > 65536 || !json.Valid(out.Payload) { return WorkflowEventDetail{}, errors.New("invalid workflow event detail") } return out, nil }