Version: 0.9.68.dev.260504
后端: 1. 阶段 3 notification 服务边界落地,新增 `cmd/notification`、`services/notification`、`gateway/notification`、`shared/contracts/notification` 和 notification port,按 userauth 同款最小手搓 zrpc 样板收口 2. notification outbox consumer、relay 和 retry loop 迁入独立服务入口,处理 `notification.feishu.requested`,gateway 改为通过 zrpc client 调用 notification 3. 清退旧单体 notification DAO/model/service/provider/runner 和 `service/events/notification_feishu.go`,旧实现不再作为活跃编译路径 4. 修复 outbox 路由归属、dispatch 启动扫描、Kafka topic 探测/投递超时、sending 租约恢复、毒消息 MarkDead 错误回传和 RPC timeout 边界 5. 同步调整 active-scheduler 触发通知事件、核心 outbox handler、MySQL 迁移边界和 notification 配置 文档: 1. 更新微服务迁移计划,将阶段 3 notification 标记为已完成,并明确下一阶段从 active-scheduler 开始
This commit is contained in:
139
backend/services/notification/internal/feishu/mock.go
Normal file
139
backend/services/notification/internal/feishu/mock.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package feishu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MockMode 描述 mock provider 下一次返回哪类结果。
|
||||
type MockMode string
|
||||
|
||||
const (
|
||||
MockModeSuccess MockMode = "success"
|
||||
MockModeTemporaryFail MockMode = "temporary_fail"
|
||||
MockModePermanentFail MockMode = "permanent_fail"
|
||||
)
|
||||
|
||||
// MockProvider 是进程内 mock provider。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 只用于本地联调、单元测试和阶段性验收;
|
||||
// 2. 不做真实 HTTP 调用,直接根据预设 mode 返回 success / temporary_fail / permanent_fail;
|
||||
// 3. 保留调用历史,方便测试断言“有没有重复发飞书”。
|
||||
type MockProvider struct {
|
||||
mu sync.Mutex
|
||||
defaultMode MockMode
|
||||
queuedModes []MockMode
|
||||
calls []SendRequest
|
||||
}
|
||||
|
||||
func NewMockProvider(defaultMode MockMode) *MockProvider {
|
||||
if defaultMode == "" {
|
||||
defaultMode = MockModeSuccess
|
||||
}
|
||||
return &MockProvider{defaultMode: defaultMode}
|
||||
}
|
||||
|
||||
func (p *MockProvider) SetDefaultMode(mode MockMode) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if mode == "" {
|
||||
mode = MockModeSuccess
|
||||
}
|
||||
p.defaultMode = mode
|
||||
}
|
||||
|
||||
// PushModes 追加一组“一次性模式”。
|
||||
//
|
||||
// 说明:
|
||||
// 1. 先进先出消费,便于测试“先失败再成功”的重试路径;
|
||||
// 2. 队列用尽后回退到 defaultMode;
|
||||
// 3. 空模式会被自动忽略,避免测试代码误塞脏数据。
|
||||
func (p *MockProvider) PushModes(modes ...MockMode) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for _, mode := range modes {
|
||||
if mode == "" {
|
||||
continue
|
||||
}
|
||||
p.queuedModes = append(p.queuedModes, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *MockProvider) Calls() []SendRequest {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
copied := make([]SendRequest, len(p.calls))
|
||||
copy(copied, p.calls)
|
||||
return copied
|
||||
}
|
||||
|
||||
// Send 按预设模式返回模拟结果。
|
||||
//
|
||||
// 步骤说明:
|
||||
// 1. 先记录本次请求,方便测试校验是否发生重复投递;
|
||||
// 2. 再按 queuedModes -> defaultMode 的顺序决定 outcome;
|
||||
// 3. 最后返回可落库审计的 request/response 摘要。
|
||||
func (p *MockProvider) Send(_ context.Context, req SendRequest) (SendResult, error) {
|
||||
p.mu.Lock()
|
||||
p.calls = append(p.calls, req)
|
||||
|
||||
mode := p.defaultMode
|
||||
if len(p.queuedModes) > 0 {
|
||||
mode = p.queuedModes[0]
|
||||
p.queuedModes = p.queuedModes[1:]
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
switch mode {
|
||||
case MockModeTemporaryFail:
|
||||
return SendResult{
|
||||
Outcome: SendOutcomeTemporaryFail,
|
||||
ErrorCode: ErrorCodeProviderTimeout,
|
||||
ErrorMessage: "mock feishu provider temporary failure",
|
||||
RequestPayload: map[string]any{
|
||||
"notification_id": req.NotificationID,
|
||||
"user_id": req.UserID,
|
||||
"preview_id": req.PreviewID,
|
||||
"target_url": req.TargetURL,
|
||||
},
|
||||
ResponsePayload: map[string]any{
|
||||
"mode": string(mode),
|
||||
"reason": "mock temporary failure",
|
||||
},
|
||||
}, nil
|
||||
case MockModePermanentFail:
|
||||
return SendResult{
|
||||
Outcome: SendOutcomePermanentFail,
|
||||
ErrorCode: ErrorCodePayloadInvalid,
|
||||
ErrorMessage: "mock feishu provider permanent failure",
|
||||
RequestPayload: map[string]any{
|
||||
"notification_id": req.NotificationID,
|
||||
"user_id": req.UserID,
|
||||
"preview_id": req.PreviewID,
|
||||
"target_url": req.TargetURL,
|
||||
},
|
||||
ResponsePayload: map[string]any{
|
||||
"mode": string(mode),
|
||||
"reason": "mock permanent failure",
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
return SendResult{
|
||||
Outcome: SendOutcomeSuccess,
|
||||
ProviderMessageID: fmt.Sprintf("mock_feishu_%d", time.Now().UnixNano()),
|
||||
RequestPayload: map[string]any{
|
||||
"notification_id": req.NotificationID,
|
||||
"user_id": req.UserID,
|
||||
"preview_id": req.PreviewID,
|
||||
"target_url": req.TargetURL,
|
||||
},
|
||||
ResponsePayload: map[string]any{
|
||||
"mode": string(MockModeSuccess),
|
||||
"status": "ok",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user