feat: complete cmpp platform phases 0-5

This commit is contained in:
hectorzhao
2026-07-01 13:22:04 +08:00
parent 824a8b334f
commit ee926fea04
86 changed files with 10490 additions and 14 deletions
+139
View File
@@ -0,0 +1,139 @@
package spike
import (
"context"
"fmt"
"sync/atomic"
"time"
"cmpp-platform/gateway/internal/queue"
"cmpp-platform/gateway/internal/tracker"
)
type Simulator struct {
tracker *tracker.Tracker
sequence atomic.Uint32
}
type RunResult struct {
Submitted int
SubmitResults int
ReceiptEvents int
Duration time.Duration
MessagesPerSec float64
}
func NewSimulator() *Simulator {
return &Simulator{tracker: tracker.New()}
}
func (s *Simulator) Submit(ctx context.Context, cmd queue.SubmitCommand) (queue.SubmitResult, queue.ReceiptEvent, error) {
if err := ctx.Err(); err != nil {
return queue.SubmitResult{}, queue.ReceiptEvent{}, err
}
sequenceID := s.sequence.Add(1)
s.tracker.TrackSubmit(cmd.MessageID, sequenceID)
gatewayMessageID := fmt.Sprintf("gw-%s", cmd.MessageID)
mapping, err := s.tracker.TrackSubmitResp(sequenceID, gatewayMessageID)
if err != nil {
return queue.SubmitResult{}, queue.ReceiptEvent{}, err
}
now := time.Now().UTC()
result := queue.SubmitResult{
Envelope: queue.Envelope{
SchemaVersion: queue.SchemaVersion,
MessageType: queue.MessageTypeSubmitResult,
TraceID: cmd.TraceID,
MessageID: mapping.MessageID,
ChannelID: cmd.ChannelID,
CreatedAt: now,
},
SequenceID: mapping.SequenceID,
GatewayMessageID: mapping.GatewayMessageID,
SubmitStatus: "accepted",
SubmittedAt: now,
}
receipt := queue.ReceiptEvent{
Envelope: queue.Envelope{
SchemaVersion: queue.SchemaVersion,
MessageType: queue.MessageTypeReceiptEvent,
TraceID: cmd.TraceID,
MessageID: mapping.MessageID,
ChannelID: cmd.ChannelID,
CreatedAt: now.Add(10 * time.Millisecond),
},
SequenceID: mapping.SequenceID,
GatewayMessageID: mapping.GatewayMessageID,
ReceiptStatus: "delivered",
RawStatus: "DELIVRD",
DeliveredAt: now.Add(10 * time.Millisecond),
}
return result, receipt, nil
}
func (s *Simulator) RunLoad(ctx context.Context, count int) (RunResult, error) {
start := time.Now()
result := RunResult{Submitted: count}
for i := 0; i < count; i++ {
cmd := NewSubmitCommand(i)
_, _, err := s.Submit(ctx, cmd)
if err != nil {
return RunResult{}, err
}
result.SubmitResults++
result.ReceiptEvents++
}
result.Duration = time.Since(start)
if result.Duration > 0 {
result.MessagesPerSec = float64(count) / result.Duration.Seconds()
}
return result, nil
}
func NewSubmitCommand(index int) queue.SubmitCommand {
now := time.Now().UTC()
messageID := fmt.Sprintf("msg-spike-%06d", index)
return queue.SubmitCommand{
Envelope: queue.Envelope{
SchemaVersion: queue.SchemaVersion,
MessageType: queue.MessageTypeSubmitCommand,
TraceID: fmt.Sprintf("trace-spike-%06d", index),
MessageID: messageID,
ChannelID: "sms-channel-cmpp-spike",
CreatedAt: now,
},
TenantID: "tenant-spike",
ApplicationID: "app-spike",
TaskID: "task-spike",
SubmitID: fmt.Sprintf("submit-spike-%06d", index),
PhoneNumber: "13800138000",
Content: "您的验证码为 1234565 分钟内有效。",
Signature: "测试平台",
TemplateID: "tpl-spike",
BillingUnits: 1,
Route: queue.Route{
ChannelCode: "CMCC-CMPP-SPIKE",
CMPPAccountCode: "cmpp-account-spike",
Priority: 10,
RateLimitPerSecond: 500,
},
CMPP: queue.CMPP{
ServiceID: "CMPP",
SrcID: "106900000000",
RegisteredDelivery: 1,
MsgFmt: 15,
FeeUserType: 2,
FeeCode: "0",
FeeType: "01",
},
Retry: queue.Retry{Attempt: 0, MaxAttempts: 3},
}
}
+48
View File
@@ -0,0 +1,48 @@
package spike
import (
"context"
"testing"
"time"
)
func TestSimulatorRunsOneMessageLifecycle(t *testing.T) {
sim := NewSimulator()
cmd := NewSubmitCommand(1)
result, receipt, err := sim.Submit(context.Background(), cmd)
if err != nil {
t.Fatalf("submit: %v", err)
}
if result.MessageID != cmd.MessageID {
t.Fatalf("submit result message id mismatch: %s != %s", result.MessageID, cmd.MessageID)
}
if result.SequenceID == 0 {
t.Fatal("expected sequence id")
}
if receipt.GatewayMessageID != result.GatewayMessageID {
t.Fatalf("receipt gateway id mismatch: %s != %s", receipt.GatewayMessageID, result.GatewayMessageID)
}
if receipt.ReceiptStatus != "delivered" {
t.Fatalf("unexpected receipt status: %s", receipt.ReceiptStatus)
}
}
func TestSimulatorLoadMeets500TPSFloor(t *testing.T) {
sim := NewSimulator()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
result, err := sim.RunLoad(ctx, 15000)
if err != nil {
t.Fatalf("run load: %v", err)
}
if result.SubmitResults != 15000 || result.ReceiptEvents != 15000 {
t.Fatalf("unexpected result counts: %+v", result)
}
if result.MessagesPerSec < 500 {
t.Fatalf("expected at least 500 msg/s, got %.2f", result.MessagesPerSec)
}
}