feat(event): publish typed OA approval schemas
This commit is contained in:
@@ -27,14 +27,58 @@ func TestPersonalOAEventListAndSchemaCommands(t *testing.T) {
|
||||
if err := list.Execute(); err != nil {
|
||||
t.Fatalf("event list --category oa error = %v", err)
|
||||
}
|
||||
for _, eventKey := range []string{
|
||||
personal.EventOAApprovalTaskCreated,
|
||||
personal.EventOAApprovalTaskFinished,
|
||||
personal.EventOAApprovalTaskRedirected,
|
||||
personal.EventOAApprovalInstanceStarted,
|
||||
personal.EventOAApprovalInstanceTerminated,
|
||||
personal.EventOAApprovalInstanceFinished,
|
||||
} {
|
||||
tests := []struct {
|
||||
eventKey string
|
||||
properties []string
|
||||
}{
|
||||
{
|
||||
eventKey: personal.EventOAApprovalTaskCreated,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "create_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: personal.EventOAApprovalTaskFinished,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "result", "create_time",
|
||||
"finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: personal.EventOAApprovalTaskRedirected,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "result", "create_time",
|
||||
"finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: personal.EventOAApprovalInstanceStarted,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "create_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: personal.EventOAApprovalInstanceTerminated,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "create_time", "finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: personal.EventOAApprovalInstanceFinished,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "result", "create_time", "finish_time",
|
||||
"event_time",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
eventKey := tt.eventKey
|
||||
if !strings.Contains(listOut.String(), eventKey) {
|
||||
t.Fatalf("OA event list missing %s:\n%s", eventKey, listOut.String())
|
||||
}
|
||||
@@ -60,12 +104,16 @@ func TestPersonalOAEventListAndSchemaCommands(t *testing.T) {
|
||||
t.Fatalf("schema body for %s = %#v", eventKey, doc["schema"])
|
||||
}
|
||||
properties, ok := schemaBody["properties"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("schema properties for %s = %#v", eventKey, schemaBody["properties"])
|
||||
if !ok || len(properties) != len(tt.properties) {
|
||||
t.Fatalf("schema properties for %s = %#v, want %d fields", eventKey, schemaBody["properties"], len(tt.properties))
|
||||
}
|
||||
payload, ok := properties["payload"].(map[string]any)
|
||||
if !ok || payload["additionalProperties"] != true {
|
||||
t.Fatalf("schema payload for %s = %#v, want open object", eventKey, properties["payload"])
|
||||
for _, name := range tt.properties {
|
||||
if _, ok := properties[name].(map[string]any); !ok {
|
||||
t.Fatalf("schema property %s for %s = %#v", name, eventKey, properties[name])
|
||||
}
|
||||
}
|
||||
if _, ok := properties["payload"]; ok {
|
||||
t.Fatalf("schema for %s exposed generic payload: %#v", eventKey, properties)
|
||||
}
|
||||
}
|
||||
if strings.Contains(listOut.String(), personal.EventMention) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:853478184de2998932c3ce94cab108a1640a96b17a9db2faedcefb169e80e690",
|
||||
"source_hash": "sha256:c2d5854a1d9b0f6570e2f60283529d84b3f7a1d231cb5f76a0007c479d40cefe",
|
||||
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
|
||||
"coverage": {
|
||||
"surface_products": 26,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:853478184de2998932c3ce94cab108a1640a96b17a9db2faedcefb169e80e690",
|
||||
"source_hash": "sha256:c2d5854a1d9b0f6570e2f60283529d84b3f7a1d231cb5f76a0007c479d40cefe",
|
||||
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
|
||||
"source_files": 160,
|
||||
"hint_files": 54,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"source_hash": "sha256:ac05d2673d85f725b952faa7bd91fba6cd9032e0e7ab4708a9dc796ffd5032b8",
|
||||
"source_hash": "sha256:0f607968472026be03b650cf7ea63e0ed9f7e12137dec2d694f3c62f2e81f837",
|
||||
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
|
||||
"catalog": {
|
||||
"agent_metadata": {
|
||||
"products_with_metadata": 26,
|
||||
"source": "embedded-skill-metadata",
|
||||
"source_hash": "sha256:853478184de2998932c3ce94cab108a1640a96b17a9db2faedcefb169e80e690",
|
||||
"source_hash": "sha256:c2d5854a1d9b0f6570e2f60283529d84b3f7a1d231cb5f76a0007c479d40cefe",
|
||||
"surface_hash": "sha256:4cf8460240b19f896c3a330c69982cbb5f57aa8576cdf30373172082eea893ed",
|
||||
"surface_products": 26,
|
||||
"surface_tools": 813,
|
||||
|
||||
@@ -121,15 +121,92 @@ type GroupLifecycleEventOutput struct {
|
||||
Payload map[string]any `json:"payload" description:"群生命周期事件业务数据,字段以服务端实际推送为准" additional_properties:"true"`
|
||||
}
|
||||
|
||||
// OAEventOutput intentionally keeps the OA payload open until stable approval
|
||||
// samples and status enums are available. Only top-level transport routing
|
||||
// metadata is removed; all unknown business fields are preserved.
|
||||
type OAEventOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
Payload map[string]any `json:"payload" description:"OA 审批事件业务数据,字段以服务端实际推送为准" additional_properties:"true"`
|
||||
type OAApprovalTaskCreatedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
TaskID string `json:"task_id" description:"审批任务 ID"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"审批任务状态"`
|
||||
CreateTime int64 `json:"create_time" description:"审批任务创建时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批任务事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type OAApprovalTaskFinishedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
TaskID string `json:"task_id" description:"审批任务 ID"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"审批任务状态"`
|
||||
Result string `json:"result" description:"审批任务处理结果,值以服务端实际推送为准"`
|
||||
CreateTime int64 `json:"create_time" description:"审批任务创建时间" format:"timestamp_ms"`
|
||||
FinishTime int64 `json:"finish_time" description:"审批任务完成时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批任务事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type OAApprovalTaskRedirectedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
TaskID string `json:"task_id" description:"原审批任务 ID"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"原审批任务状态"`
|
||||
Result string `json:"result" description:"审批任务转交结果,值以服务端实际推送为准"`
|
||||
CreateTime int64 `json:"create_time" description:"原审批任务创建时间" format:"timestamp_ms"`
|
||||
FinishTime int64 `json:"finish_time" description:"原审批任务转交完成时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批任务转交事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type OAApprovalInstanceStartedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"审批实例状态"`
|
||||
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批实例事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type OAApprovalInstanceTerminatedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"审批实例状态"`
|
||||
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
||||
FinishTime int64 `json:"finish_time" description:"审批实例终止时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批实例终止事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type OAApprovalInstanceFinishedOutput struct {
|
||||
Type string `json:"type" description:"事件类型,固定为当前 event_key"`
|
||||
EventID string `json:"event_id" description:"事件 ID,可用于去重"`
|
||||
Timestamp int64 `json:"timestamp" description:"事件发生时间戳" format:"timestamp_ms"`
|
||||
SubscribeID string `json:"subscribe_id" description:"订阅 ID"`
|
||||
ProcessInstanceID string `json:"process_instance_id" description:"审批实例 ID"`
|
||||
ProcessCode string `json:"process_code" description:"审批流程模板编码"`
|
||||
Title string `json:"title" description:"审批标题"`
|
||||
Status string `json:"status" description:"审批实例状态"`
|
||||
Result string `json:"result" description:"审批实例处理结果,值以服务端实际推送为准"`
|
||||
CreateTime int64 `json:"create_time" description:"审批实例创建时间" format:"timestamp_ms"`
|
||||
FinishTime int64 `json:"finish_time" description:"审批实例完成时间" format:"timestamp_ms"`
|
||||
EventTime int64 `json:"event_time" description:"审批实例事件业务时间" format:"timestamp_ms"`
|
||||
}
|
||||
|
||||
type GroupMemberEventOutput struct {
|
||||
@@ -241,6 +318,22 @@ type personalGroupMemberRecord struct {
|
||||
OpenDingTalkID string `json:"openDingTalkId"`
|
||||
}
|
||||
|
||||
type personalOAApprovalPayload struct {
|
||||
EventTime int64 `json:"event_time"`
|
||||
Body personalOAApprovalBody `json:"body"`
|
||||
}
|
||||
|
||||
type personalOAApprovalBody struct {
|
||||
ProcessInstanceID string `json:"processInstanceId"`
|
||||
ProcessCode string `json:"processCode"`
|
||||
TaskID string `json:"taskId"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Result string `json:"result"`
|
||||
CreateTime int64 `json:"createTime"`
|
||||
FinishTime int64 `json:"finishTime"`
|
||||
}
|
||||
|
||||
func (b *personalReactionBody) UnmarshalJSON(data []byte) error {
|
||||
// encoding/json otherwise falls back to case-insensitive field matching.
|
||||
// Read this protocol field from a map so only operOpenDingtalkId is accepted.
|
||||
@@ -366,17 +459,7 @@ func ProjectOutput(ev transport.Event) (any, error) {
|
||||
Payload: payload,
|
||||
}, nil
|
||||
case isOAEvent(eventType):
|
||||
payload, err := decodeConservativePayload(data.Payload)
|
||||
if err != nil {
|
||||
return ev, fmt.Errorf("decode personal OA payload: %w", err)
|
||||
}
|
||||
return OAEventOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
Payload: payload,
|
||||
}, nil
|
||||
return projectOAApprovalEvent(ev, base, data.Payload)
|
||||
default:
|
||||
return ev, fmt.Errorf("unsupported personal event type %q", eventType)
|
||||
}
|
||||
@@ -512,6 +595,112 @@ func projectGroupMemberEvent(ev transport.Event, base baseEventOutput, raw json.
|
||||
}, nil
|
||||
}
|
||||
|
||||
func projectOAApprovalEvent(ev transport.Event, base baseEventOutput, raw json.RawMessage) (any, error) {
|
||||
var payload personalOAApprovalPayload
|
||||
if err := decodeRequiredPayload(raw, &payload); err != nil {
|
||||
return ev, fmt.Errorf("decode personal OA payload: %w", err)
|
||||
}
|
||||
if strings.TrimSpace(payload.Body.ProcessInstanceID) == "" {
|
||||
return ev, fmt.Errorf("decode personal OA payload: processInstanceId is required")
|
||||
}
|
||||
if isOAApprovalTaskEvent(base.Type) && strings.TrimSpace(payload.Body.TaskID) == "" {
|
||||
return ev, fmt.Errorf("decode personal OA payload: taskId is required for %s", base.Type)
|
||||
}
|
||||
|
||||
switch base.Type {
|
||||
case EventOAApprovalTaskCreated:
|
||||
return OAApprovalTaskCreatedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
TaskID: payload.Body.TaskID,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
case EventOAApprovalTaskFinished:
|
||||
return OAApprovalTaskFinishedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
TaskID: payload.Body.TaskID,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
Result: payload.Body.Result,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
FinishTime: payload.Body.FinishTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
case EventOAApprovalTaskRedirected:
|
||||
return OAApprovalTaskRedirectedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
TaskID: payload.Body.TaskID,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
Result: payload.Body.Result,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
FinishTime: payload.Body.FinishTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
case EventOAApprovalInstanceStarted:
|
||||
return OAApprovalInstanceStartedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
case EventOAApprovalInstanceTerminated:
|
||||
return OAApprovalInstanceTerminatedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
FinishTime: payload.Body.FinishTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
case EventOAApprovalInstanceFinished:
|
||||
return OAApprovalInstanceFinishedOutput{
|
||||
Type: base.Type,
|
||||
EventID: base.EventID,
|
||||
Timestamp: base.Timestamp,
|
||||
SubscribeID: base.SubscribeID,
|
||||
ProcessInstanceID: payload.Body.ProcessInstanceID,
|
||||
ProcessCode: payload.Body.ProcessCode,
|
||||
Title: payload.Body.Title,
|
||||
Status: payload.Body.Status,
|
||||
Result: payload.Body.Result,
|
||||
CreateTime: payload.Body.CreateTime,
|
||||
FinishTime: payload.Body.FinishTime,
|
||||
EventTime: payload.EventTime,
|
||||
}, nil
|
||||
default:
|
||||
return ev, fmt.Errorf("unsupported personal OA event type %q", base.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRequiredPayload(raw json.RawMessage, target any) error {
|
||||
trimmed := bytes.TrimSpace(raw)
|
||||
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
||||
@@ -674,8 +863,18 @@ func outputTypeForEvent(eventKey string) reflect.Type {
|
||||
return reflect.TypeOf(GroupMemberEventOutput{})
|
||||
case isGroupLifecycleEvent(eventKey):
|
||||
return reflect.TypeOf(GroupLifecycleEventOutput{})
|
||||
case isOAEvent(eventKey):
|
||||
return reflect.TypeOf(OAEventOutput{})
|
||||
case eventKey == EventOAApprovalTaskCreated:
|
||||
return reflect.TypeOf(OAApprovalTaskCreatedOutput{})
|
||||
case eventKey == EventOAApprovalTaskFinished:
|
||||
return reflect.TypeOf(OAApprovalTaskFinishedOutput{})
|
||||
case eventKey == EventOAApprovalTaskRedirected:
|
||||
return reflect.TypeOf(OAApprovalTaskRedirectedOutput{})
|
||||
case eventKey == EventOAApprovalInstanceStarted:
|
||||
return reflect.TypeOf(OAApprovalInstanceStartedOutput{})
|
||||
case eventKey == EventOAApprovalInstanceTerminated:
|
||||
return reflect.TypeOf(OAApprovalInstanceTerminatedOutput{})
|
||||
case eventKey == EventOAApprovalInstanceFinished:
|
||||
return reflect.TypeOf(OAApprovalInstanceFinishedOutput{})
|
||||
default:
|
||||
return reflect.TypeOf(baseEventOutput{})
|
||||
}
|
||||
@@ -711,6 +910,12 @@ func isOAEvent(eventKey string) bool {
|
||||
eventKey == EventOAApprovalInstanceFinished
|
||||
}
|
||||
|
||||
func isOAApprovalTaskEvent(eventKey string) bool {
|
||||
return eventKey == EventOAApprovalTaskCreated ||
|
||||
eventKey == EventOAApprovalTaskFinished ||
|
||||
eventKey == EventOAApprovalTaskRedirected
|
||||
}
|
||||
|
||||
func schemaType(t reflect.Type) string {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
|
||||
@@ -151,25 +151,56 @@ func personalGroupMemberData(eventKey string) string {
|
||||
}
|
||||
|
||||
func personalOAData(eventKey string) string {
|
||||
return fmt.Sprintf(`{
|
||||
"eventId":"oa-event",
|
||||
"eventKey":%q,
|
||||
"occurredAtMs":1785229200123,
|
||||
"subId":"oa-data-sub",
|
||||
"payload":{
|
||||
"uid":100001,
|
||||
"CORPID":"internal-corp",
|
||||
"clientId":"internal-client",
|
||||
"filterSubId":"internal-filter",
|
||||
"bizid":"internal-biz",
|
||||
"orgId":100002,
|
||||
"sourceId":"open",
|
||||
"processInstanceId":"process-instance-1",
|
||||
"taskId":"approval-task-1",
|
||||
"futureField":{"nested":true,"uid":"business-user-1"},
|
||||
"steps":[{"name":"审批","result":"agree"}]
|
||||
}
|
||||
}`, eventKey)
|
||||
body := map[string]any{
|
||||
"processInstanceId": "process-instance-1",
|
||||
"createTime": int64(1785229100000),
|
||||
"processCode": "PROC-TEST-1",
|
||||
"title": "测试审批",
|
||||
}
|
||||
switch eventKey {
|
||||
case EventOAApprovalTaskCreated:
|
||||
body["taskId"] = "approval-task-1"
|
||||
body["status"] = "RUNNING"
|
||||
case EventOAApprovalTaskFinished:
|
||||
body["taskId"] = "approval-task-1"
|
||||
body["status"] = "FINISHED"
|
||||
body["result"] = "agree"
|
||||
body["finishTime"] = int64(1785229199000)
|
||||
case EventOAApprovalTaskRedirected:
|
||||
body["taskId"] = "approval-task-1"
|
||||
body["status"] = "FINISHED"
|
||||
body["result"] = "redirect"
|
||||
body["finishTime"] = int64(1785229199000)
|
||||
case EventOAApprovalInstanceStarted:
|
||||
body["status"] = "RUNNING"
|
||||
case EventOAApprovalInstanceTerminated:
|
||||
body["status"] = "TERMINATED"
|
||||
body["finishTime"] = int64(1785229199000)
|
||||
case EventOAApprovalInstanceFinished:
|
||||
body["status"] = "FINISHED"
|
||||
body["result"] = "agree"
|
||||
body["finishTime"] = int64(1785229199000)
|
||||
}
|
||||
data := map[string]any{
|
||||
"eventId": "oa-event",
|
||||
"eventKey": eventKey,
|
||||
"occurredAtMs": int64(1785229200123),
|
||||
"subId": "oa-data-sub",
|
||||
"payload": map[string]any{
|
||||
"uid": 100001,
|
||||
"CORPID": "internal-corp",
|
||||
"clientId": "internal-client",
|
||||
"filterSubId": "internal-filter",
|
||||
"bizid": "internal-biz",
|
||||
"orgId": 100002,
|
||||
"sourceId": "open",
|
||||
"body": body,
|
||||
"event_time": int64(1785229199000),
|
||||
"futureField": map[string]any{"nested": true},
|
||||
},
|
||||
}
|
||||
encoded, _ := json.Marshal(data)
|
||||
return string(encoded)
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageProjectOutputMessageEvents(t *testing.T) {
|
||||
@@ -419,48 +450,127 @@ func TestCrossPlatformCoverageProjectOutputGroupLifecycleEvents(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageProjectOutputOAEvents(t *testing.T) {
|
||||
for _, eventKey := range []string{
|
||||
EventOAApprovalTaskCreated,
|
||||
EventOAApprovalTaskFinished,
|
||||
EventOAApprovalTaskRedirected,
|
||||
EventOAApprovalInstanceStarted,
|
||||
EventOAApprovalInstanceTerminated,
|
||||
EventOAApprovalInstanceFinished,
|
||||
} {
|
||||
t.Run(eventKey, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
eventKey string
|
||||
want any
|
||||
}{
|
||||
{
|
||||
eventKey: EventOAApprovalTaskCreated,
|
||||
want: OAApprovalTaskCreatedOutput{
|
||||
Type: EventOAApprovalTaskCreated,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
TaskID: "approval-task-1",
|
||||
Title: "测试审批",
|
||||
Status: "RUNNING",
|
||||
CreateTime: 1785229100000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalTaskFinished,
|
||||
want: OAApprovalTaskFinishedOutput{
|
||||
Type: EventOAApprovalTaskFinished,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
TaskID: "approval-task-1",
|
||||
Title: "测试审批",
|
||||
Status: "FINISHED",
|
||||
Result: "agree",
|
||||
CreateTime: 1785229100000,
|
||||
FinishTime: 1785229199000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalTaskRedirected,
|
||||
want: OAApprovalTaskRedirectedOutput{
|
||||
Type: EventOAApprovalTaskRedirected,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
TaskID: "approval-task-1",
|
||||
Title: "测试审批",
|
||||
Status: "FINISHED",
|
||||
Result: "redirect",
|
||||
CreateTime: 1785229100000,
|
||||
FinishTime: 1785229199000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceStarted,
|
||||
want: OAApprovalInstanceStartedOutput{
|
||||
Type: EventOAApprovalInstanceStarted,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
Title: "测试审批",
|
||||
Status: "RUNNING",
|
||||
CreateTime: 1785229100000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceTerminated,
|
||||
want: OAApprovalInstanceTerminatedOutput{
|
||||
Type: EventOAApprovalInstanceTerminated,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
Title: "测试审批",
|
||||
Status: "TERMINATED",
|
||||
CreateTime: 1785229100000,
|
||||
FinishTime: 1785229199000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceFinished,
|
||||
want: OAApprovalInstanceFinishedOutput{
|
||||
Type: EventOAApprovalInstanceFinished,
|
||||
EventID: "oa-event",
|
||||
Timestamp: 1785229200123,
|
||||
SubscribeID: "outer-sub",
|
||||
ProcessInstanceID: "process-instance-1",
|
||||
ProcessCode: "PROC-TEST-1",
|
||||
Title: "测试审批",
|
||||
Status: "FINISHED",
|
||||
Result: "agree",
|
||||
CreateTime: 1785229100000,
|
||||
FinishTime: 1785229199000,
|
||||
EventTime: 1785229199000,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.eventKey, func(t *testing.T) {
|
||||
projected, err := ProjectOutput(transport.Event{
|
||||
EventID: "outer-event",
|
||||
EventBornTime: 11,
|
||||
EventType: eventKey,
|
||||
EventType: tt.eventKey,
|
||||
SubscribeID: "outer-sub",
|
||||
Data: personalOAData(eventKey),
|
||||
Data: personalOAData(tt.eventKey),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ProjectOutput() error = %v", err)
|
||||
}
|
||||
got, ok := projected.(OAEventOutput)
|
||||
if !ok {
|
||||
t.Fatalf("ProjectOutput() type = %T, want OAEventOutput", projected)
|
||||
}
|
||||
if got.Type != eventKey || got.EventID != "oa-event" || got.Timestamp != 1785229200123 || got.SubscribeID != "outer-sub" {
|
||||
t.Fatalf("common fields = %#v", got)
|
||||
}
|
||||
for _, internal := range []string{"uid", "CORPID", "clientId", "filterSubId", "bizid", "orgId", "sourceId"} {
|
||||
if _, ok := got.Payload[internal]; ok {
|
||||
t.Fatalf("payload retained internal field %q: %#v", internal, got.Payload)
|
||||
}
|
||||
}
|
||||
if got.Payload["processInstanceId"] != "process-instance-1" || got.Payload["taskId"] != "approval-task-1" {
|
||||
t.Fatalf("payload lost known business fields: %#v", got.Payload)
|
||||
}
|
||||
future, ok := got.Payload["futureField"].(map[string]any)
|
||||
if !ok || future["nested"] != true || future["uid"] != "business-user-1" {
|
||||
t.Fatalf("payload lost unknown or nested business fields: %#v", got.Payload["futureField"])
|
||||
}
|
||||
steps, ok := got.Payload["steps"].([]any)
|
||||
if !ok || len(steps) != 1 {
|
||||
t.Fatalf("payload lost unknown array field: %#v", got.Payload["steps"])
|
||||
if !reflect.DeepEqual(projected, tt.want) {
|
||||
t.Fatalf("ProjectOutput() = %#v, want %#v", projected, tt.want)
|
||||
}
|
||||
assertNoInternalActionFields(t, projected)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -479,9 +589,9 @@ func TestCrossPlatformCoverageProjectOutputOADecodesDoublyWrappedJSONString(t *t
|
||||
if err != nil {
|
||||
t.Fatalf("ProjectOutput() error = %v", err)
|
||||
}
|
||||
got, ok := projected.(OAEventOutput)
|
||||
got, ok := projected.(OAApprovalTaskCreatedOutput)
|
||||
if !ok {
|
||||
t.Fatalf("ProjectOutput() type = %T, want OAEventOutput", projected)
|
||||
t.Fatalf("ProjectOutput() type = %T, want OAApprovalTaskCreatedOutput", projected)
|
||||
}
|
||||
if got.Type != EventOAApprovalTaskCreated || got.EventID != "oa-event" || got.SubscribeID != "oa-data-sub" {
|
||||
t.Fatalf("ProjectOutput() = %#v", got)
|
||||
@@ -651,6 +761,9 @@ func TestCrossPlatformCoverageProjectOutputRejectsInvalidOAPayloads(t *testing.T
|
||||
{name: "empty object", payload: `,"payload":{}`},
|
||||
{name: "array", payload: `,"payload":[]`},
|
||||
{name: "string", payload: `,"payload":"invalid"`},
|
||||
{name: "missing body", payload: `,"payload":{"event_time":1}`},
|
||||
{name: "null body", payload: `,"payload":{"body":null,"event_time":1}`},
|
||||
{name: "empty body", payload: `,"payload":{"body":{},"event_time":1}`},
|
||||
}
|
||||
for _, eventKey := range []string{
|
||||
EventOAApprovalTaskCreated,
|
||||
@@ -683,6 +796,48 @@ func TestCrossPlatformCoverageProjectOutputRejectsInvalidOAPayloads(t *testing.T
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageProjectOutputRejectsOAWithoutStableIDs(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
eventKey string
|
||||
body string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "missing process instance",
|
||||
eventKey: EventOAApprovalInstanceStarted,
|
||||
body: `{"status":"RUNNING"}`,
|
||||
want: "processInstanceId is required",
|
||||
},
|
||||
{
|
||||
name: "missing task",
|
||||
eventKey: EventOAApprovalTaskCreated,
|
||||
body: `{"processInstanceId":"process-instance-1","status":"RUNNING"}`,
|
||||
want: "taskId is required",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ev := transport.Event{
|
||||
EventID: "outer-event",
|
||||
EventType: tt.eventKey,
|
||||
Data: fmt.Sprintf(
|
||||
`{"eventKey":%q,"payload":{"body":%s,"event_time":1}}`,
|
||||
tt.eventKey,
|
||||
tt.body,
|
||||
),
|
||||
}
|
||||
projected, err := ProjectOutput(ev)
|
||||
if err == nil || !strings.Contains(err.Error(), tt.want) {
|
||||
t.Fatalf("ProjectOutput() error = %v, want %q", err, tt.want)
|
||||
}
|
||||
if got, ok := projected.(transport.Event); !ok || !reflect.DeepEqual(got, ev) {
|
||||
t.Fatalf("ProjectOutput() fallback = %#v, want %#v", projected, ev)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageProjectOutputDecodesWrappedJSONString(t *testing.T) {
|
||||
wrapped, err := json.Marshal(personalMessageData(EventSingleChat))
|
||||
if err != nil {
|
||||
|
||||
@@ -474,41 +474,91 @@ func TestGroupLifecycleSchemaDocumentsUseConservativePayload(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAEventSchemaDocumentsUseConservativePayload(t *testing.T) {
|
||||
wantProperties := []string{"type", "event_id", "timestamp", "subscribe_id", "payload"}
|
||||
for _, eventKey := range []string{
|
||||
EventOAApprovalTaskCreated,
|
||||
EventOAApprovalTaskFinished,
|
||||
EventOAApprovalTaskRedirected,
|
||||
EventOAApprovalInstanceStarted,
|
||||
EventOAApprovalInstanceTerminated,
|
||||
EventOAApprovalInstanceFinished,
|
||||
} {
|
||||
t.Run(eventKey, func(t *testing.T) {
|
||||
def, ok := Lookup(eventKey)
|
||||
func TestOAEventSchemaDocumentsMatchOutputDTO(t *testing.T) {
|
||||
tests := []struct {
|
||||
eventKey string
|
||||
properties []string
|
||||
}{
|
||||
{
|
||||
eventKey: EventOAApprovalTaskCreated,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "create_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalTaskFinished,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "result", "create_time",
|
||||
"finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalTaskRedirected,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "task_id", "title", "status", "result", "create_time",
|
||||
"finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceStarted,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "create_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceTerminated,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "create_time", "finish_time", "event_time",
|
||||
},
|
||||
},
|
||||
{
|
||||
eventKey: EventOAApprovalInstanceFinished,
|
||||
properties: []string{
|
||||
"type", "event_id", "timestamp", "subscribe_id", "process_instance_id",
|
||||
"process_code", "title", "status", "result", "create_time", "finish_time",
|
||||
"event_time",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.eventKey, func(t *testing.T) {
|
||||
def, ok := Lookup(tt.eventKey)
|
||||
if !ok {
|
||||
t.Fatalf("Lookup(%q) failed", eventKey)
|
||||
t.Fatalf("Lookup(%q) failed", tt.eventKey)
|
||||
}
|
||||
doc := BuildSchemaDocumentForMode(def, true)
|
||||
if doc.JQRootPath != "." {
|
||||
t.Fatalf("jq_root_path = %q, want .", doc.JQRootPath)
|
||||
}
|
||||
props, ok := doc.Schema["properties"].(map[string]any)
|
||||
if !ok || len(props) != len(wantProperties) {
|
||||
t.Fatalf("schema.properties = %#v, want exactly %d fields", doc.Schema["properties"], len(wantProperties))
|
||||
if !ok || len(props) != len(tt.properties) {
|
||||
t.Fatalf("schema.properties = %#v, want exactly %d fields", doc.Schema["properties"], len(tt.properties))
|
||||
}
|
||||
for _, name := range wantProperties {
|
||||
for _, name := range tt.properties {
|
||||
if _, ok := props[name].(map[string]any); !ok {
|
||||
t.Fatalf("schema.properties.%s = %#v, want object", name, props[name])
|
||||
}
|
||||
}
|
||||
eventType := props["type"].(map[string]any)
|
||||
if !reflect.DeepEqual(eventType["enum"], []string{eventKey}) {
|
||||
t.Fatalf("schema.properties.type.enum = %#v, want %q", eventType["enum"], eventKey)
|
||||
if !reflect.DeepEqual(eventType["enum"], []string{tt.eventKey}) {
|
||||
t.Fatalf("schema.properties.type.enum = %#v, want %q", eventType["enum"], tt.eventKey)
|
||||
}
|
||||
payload := props["payload"].(map[string]any)
|
||||
if payload["type"] != "object" || payload["additionalProperties"] != true {
|
||||
t.Fatalf("schema.properties.payload = %#v, want open object", payload)
|
||||
if _, ok := props["payload"]; ok {
|
||||
t.Fatalf("schema.properties exposed generic payload: %#v", props)
|
||||
}
|
||||
for _, name := range []string{"timestamp", "create_time", "finish_time", "event_time"} {
|
||||
property, exists := props[name].(map[string]any)
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if property["type"] != "integer" || property["format"] != "timestamp_ms" {
|
||||
t.Fatalf("schema.properties.%s = %#v, want timestamp_ms integer", name, property)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
1. 从用户意图选择事件码;人名或群名先解析成必填 ID。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key> --flatten`,读取 `schema.properties`;此模式的 `jq_root_path` 为 `.`。
|
||||
3. 启动 `dws event consume <event_key> [event_key...] ... --flatten -f ndjson`。单事件等待 `ready event_key=...`;多事件记录每条 `subscription event_key=... subscribe_id=...`,再等待整体 `ready event_count=...`。不要用 `sleep` 猜测。
|
||||
4. stdout 每行是一个扁平事件 JSON;消息、动作及群成员加入/退出事件读取顶层业务字段。群标题变更、群解散和 OA 事件只读取公共字段和 `payload` 中实际存在的字段。
|
||||
4. stdout 每行是一个扁平事件 JSON;消息、动作、群成员加入/退出及 OA 审批事件读取顶层业务字段。群标题变更和群解散只读取公共字段和 `payload` 中实际存在的字段。
|
||||
5. 需要确认监听状态时运行 `dws event status --event <event_key>`,查看 `Subscriptions` 和 `Consumers`。
|
||||
6. 任务完成后优雅结束 consume;本次新建的订阅会自动取消。复用已有订阅或需要从外部主动取消时,先运行 `dws event stop <subscribe_id> --dry-run`,向用户确认后再以 `--yes` 执行;自测可在 consume 加 `--max-events` 或 `--duration` 自动退出。
|
||||
|
||||
@@ -219,7 +219,7 @@ dws event stop --all --yes
|
||||
- 表情回应事件直接读取 `operator/operator_open_dingtalk_id/reaction_name/reaction_text/operation_type/operation_time`。
|
||||
- 群成员加入/退出事件读取 `conversation_id/operator/operator_open_dingtalk_id/members/event_time`。`operator` 是执行操作的人,`members` 是本次加入或退出的成员数组,成员项包含 `nick/open_dingtalk_id`;系统操作或成员自行退出时操作人字段可能为空。
|
||||
- 群标题变更和群解散当前只承诺 `type/event_id/timestamp/subscribe_id/payload`;以实际 `payload` 为准,不猜测群标题、操作者等字段。
|
||||
- OA 事件同样只承诺 `type/event_id/timestamp/subscribe_id/payload`。`payload` 是允许任意业务字段的开放对象,内部路由字段会被移除;不要猜测 `taskId`、`processInstanceId` 或审批完成状态枚举。payload 缺失或非法时 stderr 会输出 warning,stdout 回退为原始 transport envelope。
|
||||
- OA 事件读取顶层 `process_instance_id/process_code/title/status/create_time/event_time`;任务事件另有 `task_id`,完成、转交或终止事件按对应 schema 提供 `finish_time`,任务完成、任务转交和实例完成还提供 `result`。`status/result` 保留服务端实际值,不推断完整枚举;缺少稳定 ID 或 payload 非法时 stderr 会输出 warning,stdout 回退为原始 transport envelope。
|
||||
- 图片、文件等媒体消息的 `content` 可能是可读描述;合并转发媒体的下载定位信息位于对应 `forward_messages[].content`。需要实际媒体文件时调用 `dws chat message download-media`。
|
||||
- 正常动作事件输出不含内部 `payload/uid/corpid/clientId/filterSubId/bizid`;原始排查才使用 `-f raw` 或 `--debug-raw-events`。
|
||||
- 自己发的消息不作为事件回来(`isSelfLoop` 过滤);自发验证会看到 0 事件,测试投递使用别人或机器人发消息。
|
||||
|
||||
@@ -87,7 +87,7 @@ description: 钉钉个人 IM 事件与 OA 审批事件长连接监听、订阅
|
||||
1. 从用户意图选择事件码;人名或群名先解析成必填 ID。
|
||||
2. 需要了解字段时运行 `dws event schema <event_key> --flatten`,读取 `schema.properties`;此模式的 `jq_root_path` 为 `.`。
|
||||
3. 启动 `dws event consume <event_key> [event_key...] ... --flatten -f ndjson`。单事件等待 `[event] ready event_key=<key> bus_pid=<pid> subscribe_id=<id>`;多事件先记录每条 `[event] subscription event_key=<key> subscribe_id=<id>`,再等待 `[event] ready event_count=<n> bus_pid=<pid>`。不要用 `sleep` 猜测。
|
||||
4. stdout 每行是一个扁平事件 JSON;消息、动作及群成员加入/退出事件直接读取顶层业务字段。群标题变更、群解散和 OA 事件只读取公共字段与 `payload` 中实际存在的字段。
|
||||
4. stdout 每行是一个扁平事件 JSON;消息、动作、群成员加入/退出及 OA 审批事件直接读取顶层业务字段。群标题变更和群解散只读取公共字段与 `payload` 中实际存在的字段。
|
||||
5. 需要确认监听状态时运行 `dws event status --event <event_key>`,查看 `Subscriptions` 和 `Consumers`。
|
||||
6. 任务完成后优雅结束 consume;本次新建的订阅会自动取消。复用已有订阅或需要从外部主动取消时,先用 `dws event stop <subscribe_id> --dry-run` 预览,向用户确认后再加 `--yes`;临时测试可用 `--max-events` 或 `--duration` 自动退出。
|
||||
|
||||
@@ -241,7 +241,7 @@ dws event consume user_im_message_receive_o2o \
|
||||
- 引用回复读取可选的 `quoted_message`;合并转发读取可选的 `forward_messages` 数组。两者保留内部消息的 `message_id/conversation_id/sender/sender_open_dingtalk_id/content/create_time`;不要通过“聊天记录”等本地化外层文案识别或拆分合并转发。
|
||||
- 群成员加入/退出事件读取顶层 `conversation_id`、`operator`、`operator_open_dingtalk_id`、`members`、`event_time`。`operator` 是执行操作的人,`members` 是本次加入或退出的成员数组;成员项读取 `nick` 和 `open_dingtalk_id`。系统操作或成员自行退出时,操作人字段可能为空。
|
||||
- 群标题变更和群解散当前只承诺顶层 `type/event_id/timestamp/subscribe_id/payload`。读取 `payload` 时以实际键为准,不猜测群标题、操作者等尚未确认的字段;完整原始协议用 `-f raw` 或 `--debug-raw-events` 排查。
|
||||
- OA 事件当前同样只承诺顶层 `type/event_id/timestamp/subscribe_id/payload`;`payload` 保留服务端实际业务字段并移除内部路由字段。不要猜测 `taskId`、`processInstanceId` 或完成状态枚举;payload 缺失或非法时会告警并回退输出原始 transport envelope。
|
||||
- OA 事件读取顶层 `process_instance_id/process_code/title/status/create_time/event_time`。任务事件另有 `task_id`;完成、转交或终止事件按对应 schema 提供 `finish_time`,任务完成、任务转交和实例完成还提供 `result`。`status/result` 的值以服务端实际推送为准,不自行补充枚举;缺少稳定 ID 或 payload 非法时会告警并回退输出原始 transport envelope。
|
||||
- 群自动回复使用事件顶层 `conversation_id`;单聊自动回复使用顶层 `sender_open_dingtalk_id`。
|
||||
- 已读事件读取顶层 `reader`、`reader_open_dingtalk_id`、`read_time`;撤回事件读取 `recaller`、`recaller_open_dingtalk_id`、`recall_time`。
|
||||
- 表情回应事件读取顶层 `operator`、`operator_open_dingtalk_id`、`reaction_name`、`reaction_text`、`operation_type`、`operation_time`。
|
||||
@@ -252,4 +252,4 @@ dws event consume user_im_message_receive_o2o \
|
||||
| Topic | Reference | Coverage |
|
||||
|---|---|---|
|
||||
| IM | [references/event-im.md](references/event-im.md) | 十六类个人 IM 事件命令、参数、生命周期、输出解析、自测和排障 |
|
||||
| OA | [references/event-oa.md](references/event-oa.md) | 六类个人 OA 审批事件、无参数订阅、多事件消费和保守 payload 契约 |
|
||||
| OA | [references/event-oa.md](references/event-oa.md) | 六类个人 OA 审批事件、无参数订阅、多事件消费和稳定扁平输出字段 |
|
||||
|
||||
@@ -41,7 +41,7 @@ dws auth login
|
||||
| “查看 OA 事件目录” | `dws event list --category oa` |
|
||||
| “查看 OA 事件输出字段” | 对对应事件运行 `dws event schema <event_key> --flatten` |
|
||||
|
||||
三个审批任务事件分别表达任务已创建、已完成和已转交;三个审批实例事件分别表达实例已发起、已终止和已完成。首版不推断审批结果、任务 ID、实例 ID、转交对象或终止原因的具体 payload 字段。
|
||||
三个审批任务事件分别表达任务已创建、已完成和已转交;三个审批实例事件分别表达实例已发起、已终止和已完成。扁平字段来自六类事件的预发联调样本;`status` 和 `result` 保留服务端原值,不把当前样本值推断为完整枚举。
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -85,7 +85,7 @@ dws event consume \
|
||||
|
||||
## Output contract
|
||||
|
||||
`--flatten` 模式只承诺以下稳定结构:
|
||||
`--flatten` 模式的所有 OA 事件都包含以下顶层字段:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -93,14 +93,52 @@ dws event consume \
|
||||
"event_id": "...",
|
||||
"timestamp": 0,
|
||||
"subscribe_id": "...",
|
||||
"payload": {}
|
||||
"process_instance_id": "...",
|
||||
"process_code": "...",
|
||||
"title": "...",
|
||||
"status": "RUNNING",
|
||||
"create_time": 0,
|
||||
"event_time": 0
|
||||
}
|
||||
```
|
||||
|
||||
- `type` 是当前 event key;`event_id` 可用于去重;`timestamp` 是事件发生时间戳;`subscribe_id` 标识对应的独立订阅。
|
||||
- `payload` 是开放业务对象,schema 使用 `additionalProperties=true`。未知业务字段原样保留,内部路由字段 `uid/corpid/clientId/filterSubId/bizid/orgId/sourceId` 会被移除。
|
||||
- 不要假设 `payload` 一定包含 `taskId`、`processInstanceId`、`processCode`、审批结果或完成状态,也不要猜测状态枚举。始终以实际 payload 和 `dws event schema <event_key> --flatten` 为准。
|
||||
- payload 缺失、为空或无法解析时,consume 会在 stderr 记录 warning,并把原始 transport envelope 写到 stdout,保证事件不被静默丢弃。
|
||||
- `type` 是当前 event key;`event_id` 可用于去重;`timestamp` 是 transport 事件发生时间;`subscribe_id` 标识对应的独立订阅。
|
||||
- `process_instance_id` 是审批实例 ID,可传给 OA 审批命令的 `--instance-id`;`process_code` 是审批流程模板编码。
|
||||
- `create_time`、`finish_time` 和 `event_time` 都是毫秒时间戳。`event_time` 是审批业务事件时间,`timestamp` 是 transport 事件时间。
|
||||
- 六类事件的额外字段如下;具体事件始终以 `dws event schema <event_key> --flatten` 为准。
|
||||
|
||||
| 事件 | 额外顶层字段 |
|
||||
|---|---|
|
||||
| `user_oa_approval_task_created` | `task_id` |
|
||||
| `user_oa_approval_task_finished` | `task_id`、`result`、`finish_time` |
|
||||
| `user_oa_approval_task_redirected` | `task_id`、`result`、`finish_time` |
|
||||
| `user_oa_approval_instance_started` | 无 |
|
||||
| `user_oa_approval_instance_terminated` | `finish_time` |
|
||||
| `user_oa_approval_instance_finished` | `result`、`finish_time` |
|
||||
|
||||
任务完成事件示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "user_oa_approval_task_finished",
|
||||
"event_id": "...",
|
||||
"timestamp": 0,
|
||||
"subscribe_id": "...",
|
||||
"process_instance_id": "...",
|
||||
"process_code": "...",
|
||||
"task_id": "...",
|
||||
"title": "测试审批",
|
||||
"status": "FINISHED",
|
||||
"result": "agree",
|
||||
"create_time": 0,
|
||||
"finish_time": 0,
|
||||
"event_time": 0
|
||||
}
|
||||
```
|
||||
|
||||
- `task_id` 是当前审批任务 ID,可传给接受任务 ID 的 OA 审批命令。
|
||||
- `status` 和 `result` 是服务端字符串;不要只根据当前样本把 `RUNNING/FINISHED/TERMINATED` 或 `agree/redirect` 写成封闭枚举。
|
||||
- payload 缺失、为空、缺少对应事件的稳定 ID 或无法解析时,consume 会在 stderr 记录 warning,并把原始 transport envelope 写到 stdout,保证事件不被静默丢弃。
|
||||
- 不传 `--flatten` 时保持兼容 transport envelope,业务 payload 位于 `.data | fromjson`。需要联调完整原始协议时使用不带 `--flatten` 的 `-f raw` 或 `--debug-raw-events`。
|
||||
|
||||
## Lifecycle
|
||||
|
||||
Reference in New Issue
Block a user