feat: harden CMPP delivery and platform workflows

This commit is contained in:
hectorzhao
2026-07-20 18:07:29 +08:00
parent 80fb5a8f53
commit f02c33cbb7
61 changed files with 1834 additions and 281 deletions
+75 -52
View File
@@ -44,18 +44,25 @@ type authRequest struct {
}
type submitRequest struct {
Account string `json:"account"`
Account string `json:"account"`
PhoneNumber string `json:"phoneNumber,omitempty"`
PhoneNumbers []string `json:"phoneNumbers,omitempty"`
Content string `json:"content"`
SrcID string `json:"srcId,omitempty"`
DestID string `json:"destId,omitempty"`
SequenceID uint32 `json:"sequenceId,omitempty"`
RemoteIP string `json:"remoteIp,omitempty"`
}
type submitResponseMessage struct {
PhoneNumber string `json:"phoneNumber"`
Content string `json:"content"`
SrcID string `json:"srcId,omitempty"`
DestID string `json:"destId,omitempty"`
SequenceID uint32 `json:"sequenceId,omitempty"`
RemoteIP string `json:"remoteIp,omitempty"`
MessageID string `json:"messageId"`
}
type submitResponse struct {
Accepted bool `json:"accepted"`
MessageID string `json:"messageId"`
Accepted bool `json:"accepted"`
MessageID string `json:"messageId"`
Messages []submitResponseMessage `json:"messages,omitempty"`
}
type authResponse struct {
@@ -68,17 +75,18 @@ type authResponse struct {
}
type DownstreamReceipt struct {
DeliveryID string `json:"deliveryId,omitempty"`
Account string `json:"account,omitempty"`
ApplicationID string `json:"applicationId,omitempty"`
MessageID string `json:"messageId"`
GatewayMessageID string `json:"gatewayMessageId,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
ReceiptStatus string `json:"receiptStatus"`
RawStatus string `json:"rawStatus,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
SubmitSequenceID uint32 `json:"submitSequenceId,omitempty"`
DeliveredAt string `json:"deliveredAt,omitempty"`
DeliveryID string `json:"deliveryId,omitempty"`
Account string `json:"account,omitempty"`
ApplicationID string `json:"applicationId,omitempty"`
MessageID string `json:"messageId"`
GatewayMessageID string `json:"gatewayMessageId,omitempty"`
PhoneNumber string `json:"phoneNumber,omitempty"`
ReceiptStatus string `json:"receiptStatus"`
RawStatus string `json:"rawStatus,omitempty"`
ErrorCode string `json:"errorCode,omitempty"`
SubmitSequenceID uint32 `json:"submitSequenceId,omitempty"`
SubmitGroupMessageID string `json:"submitGroupMessageId,omitempty"`
DeliveredAt string `json:"deliveredAt,omitempty"`
}
type DownstreamUplink struct {
@@ -267,9 +275,13 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
setInboundSubmitResponse(response.Packer, 0, 9)
return false, nil
}
phones := make([]string, len(req.destTerminalIDs))
for index, destination := range req.destTerminalIDs {
phones[index] = strings.TrimSpace(strings.TrimRight(destination, "\x00"))
}
phone := ""
if len(req.destTerminalIDs) > 0 {
phone = strings.TrimRight(req.destTerminalIDs[0], "\x00")
if len(phones) > 0 {
phone = phones[0]
}
remote := packet.Conn.Conn.RemoteAddr()
clientProtocol := defaultString(session.protocol, req.protocol)
@@ -290,13 +302,14 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
contentHash := fmt.Sprintf("%x", md5.Sum([]byte(content)))
startedAt := time.Now()
result, err := s.submit(remote, submitRequest{
Account: account,
PhoneNumber: phone,
Content: content,
SrcID: req.srcID,
DestID: phone,
SequenceID: req.sequenceID,
RemoteIP: remoteIP(remote),
Account: account,
PhoneNumber: phone,
PhoneNumbers: phones,
Content: content,
SrcID: req.srcID,
DestID: phone,
SequenceID: req.sequenceID,
RemoteIP: remoteIP(remote),
})
if err != nil || !result.Accepted {
reason := "api returned accepted=false"
@@ -312,24 +325,34 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
}
gatewayMsgID := messageIDFrom(result.MessageID, req.sequenceID)
setInboundSubmitResponse(response.Packer, gatewayMsgID, 0)
rememberDownstream(downstreamSession{
messageID: result.MessageID,
account: account,
enterpriseCode: session.enterpriseCode,
protocol: clientProtocol,
srcID: strings.TrimSpace(req.srcID),
phoneNumber: phone,
gatewayMsgID: gatewayMsgID,
remoteIP: remoteIP(remote),
connectedAt: time.Now().UTC(),
connectionID: session.connectionID,
conn: packet.Conn,
mu: &sync.Mutex{},
presence: s.PresenceStore,
instanceID: s.gatewayInstanceID(),
report: session.report,
deliveryReport: session.deliveryReport,
})
responseMessages := result.Messages
if len(responseMessages) == 0 {
responseMessages = []submitResponseMessage{{PhoneNumber: phone, MessageID: result.MessageID}}
}
for index, acceptedMessage := range responseMessages {
acceptedPhone := strings.TrimSpace(acceptedMessage.PhoneNumber)
if acceptedPhone == "" && index < len(phones) {
acceptedPhone = phones[index]
}
rememberDownstream(downstreamSession{
messageID: acceptedMessage.MessageID,
account: account,
enterpriseCode: session.enterpriseCode,
protocol: clientProtocol,
srcID: strings.TrimSpace(req.srcID),
phoneNumber: acceptedPhone,
gatewayMsgID: gatewayMsgID,
remoteIP: remoteIP(remote),
connectedAt: time.Now().UTC(),
connectionID: session.connectionID,
conn: packet.Conn,
mu: &sync.Mutex{},
presence: s.PresenceStore,
instanceID: s.gatewayInstanceID(),
report: session.report,
deliveryReport: session.deliveryReport,
})
}
if current := findSessionByConn(packet.Conn); current != nil && current.report != nil {
go current.report(current, "submit", "")
}
@@ -344,8 +367,8 @@ func (s Server) handleSubmit(response *cmpp.Response, packet *cmpp.Packet, logge
}()
}
logger.Printf(
"cmpp inbound event=submit_accepted protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s result=0 message_id=%s gateway_message_id=%d duration_ms=%d content_chars=%d content_hash=%s",
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, result.MessageID, gatewayMsgID, time.Since(startedAt).Milliseconds(), len([]rune(content)), contentHash,
"cmpp inbound event=submit_accepted protocol=%s packet_type=%s account=%s remote=%s seq=%d phone=%s dest_count=%d accepted_count=%d result=0 message_id=%s gateway_message_id=%d duration_ms=%d content_chars=%d content_hash=%s",
clientProtocol, req.protocol, account, remote, req.sequenceID, phone, len(phones), len(responseMessages), result.MessageID, gatewayMsgID, time.Since(startedAt).Milliseconds(), len([]rune(content)), contentHash,
)
return false, nil
}
@@ -742,9 +765,9 @@ func forgetDownstream(session *downstreamSession) {
return
}
downstreamRegistry.Lock()
if session.messageID != "" {
if current := downstreamRegistry.byMessageID[session.messageID]; current == session {
delete(downstreamRegistry.byMessageID, session.messageID)
for messageID, current := range downstreamRegistry.byMessageID {
if current != nil && current.conn == session.conn {
delete(downstreamRegistry.byMessageID, messageID)
}
}
if session.account != "" {
@@ -1003,7 +1026,7 @@ func recoverReceiptSession(event DownstreamReceipt) *downstreamSession {
}
recovered := *accountSession
recovered.messageID = event.MessageID
recovered.gatewayMsgID = messageIDFrom(event.MessageID, event.SubmitSequenceID)
recovered.gatewayMsgID = messageIDFrom(defaultString(event.SubmitGroupMessageID, event.MessageID), event.SubmitSequenceID)
return &recovered
}