fix: harden dependencies and downstream delivery
This commit is contained in:
@@ -93,6 +93,9 @@ type DownstreamUplink struct {
|
||||
|
||||
type DownstreamSendResult struct {
|
||||
Sent bool `json:"sent"`
|
||||
Retryable bool `json:"retryable"`
|
||||
ReasonCode string `json:"reasonCode,omitempty"`
|
||||
ErrorMessage string `json:"errorMessage,omitempty"`
|
||||
ConnectionID string `json:"connectionId,omitempty"`
|
||||
SequenceID string `json:"sequenceId,omitempty"`
|
||||
MessageID string `json:"messageId,omitempty"`
|
||||
@@ -544,7 +547,20 @@ func (s Server) flushPending(account string, logger *log.Logger) (pendingFlushRe
|
||||
result.DeliveredCount++
|
||||
continue
|
||||
}
|
||||
result.WaitingCount++
|
||||
errorMessage := defaultString(sendResult.ErrorMessage, "gateway did not complete downstream delivery")
|
||||
failureType := "unrecoverable"
|
||||
if sendResult.Retryable {
|
||||
failureType = "send_failed"
|
||||
result.WaitingCount++
|
||||
} else {
|
||||
result.FailedCount++
|
||||
}
|
||||
result.LastError = errorMessage
|
||||
_ = s.post(context.Background(), "/gateway/events/downstream/failed", map[string]string{
|
||||
"id": delivery.ID,
|
||||
"errorMessage": errorMessageWithCode(errorMessage, sendResult.ReasonCode),
|
||||
"failureType": failureType,
|
||||
}, nil)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -776,6 +792,10 @@ func (s Server) recoverPendingCandidates(logger *log.Logger) {
|
||||
status.State = "partial"
|
||||
status.LastError = result.LastError
|
||||
status.FailureCategory = recoveryFailureCategory(status.State, status.LastError, status.LastSkipReason)
|
||||
case result.FailedCount > 0:
|
||||
status.State = "failed"
|
||||
status.LastError = result.LastError
|
||||
status.FailureCategory = recoveryFailureCategory(status.State, status.LastError, status.LastSkipReason)
|
||||
default:
|
||||
status.State = "success"
|
||||
status.LastError = ""
|
||||
@@ -861,7 +881,7 @@ func PushReceipt(event DownstreamReceipt) (bool, error) {
|
||||
}
|
||||
|
||||
func PushReceiptWithResult(event DownstreamReceipt) (DownstreamSendResult, error) {
|
||||
return pushReceiptWithResult(event, false)
|
||||
return pushReceiptWithResult(event, true)
|
||||
}
|
||||
|
||||
func pushReceiptWithResult(event DownstreamReceipt, allowRecovery bool) (DownstreamSendResult, error) {
|
||||
@@ -870,7 +890,25 @@ func pushReceiptWithResult(event DownstreamReceipt, allowRecovery bool) (Downstr
|
||||
session = recoverReceiptSession(event)
|
||||
}
|
||||
if session == nil {
|
||||
return DownstreamSendResult{}, nil
|
||||
if event.SubmitSequenceID == 0 {
|
||||
return DownstreamSendResult{
|
||||
Retryable: false,
|
||||
ReasonCode: "MISSING_SUBMIT_SEQUENCE_ID",
|
||||
ErrorMessage: "历史回执缺少原 Submit Sequence_Id,无法重建 Msg_Id,系统已终止重投",
|
||||
}, nil
|
||||
}
|
||||
if strings.TrimSpace(event.MessageID) == "" || strings.TrimSpace(event.Account) == "" {
|
||||
return DownstreamSendResult{
|
||||
Retryable: false,
|
||||
ReasonCode: "INVALID_RECEIPT_PAYLOAD",
|
||||
ErrorMessage: "状态回执缺少平台消息 ID 或客户账号,系统已终止重投",
|
||||
}, nil
|
||||
}
|
||||
return DownstreamSendResult{
|
||||
Retryable: true,
|
||||
ReasonCode: "CLIENT_DISCONNECTED",
|
||||
ErrorMessage: "下游客户端当前未连接,等待自动重试",
|
||||
}, nil
|
||||
}
|
||||
stat := strings.TrimSpace(event.RawStatus)
|
||||
if stat == "" {
|
||||
@@ -934,7 +972,11 @@ func PushUplink(event DownstreamUplink) (bool, error) {
|
||||
func PushUplinkWithResult(event DownstreamUplink) (DownstreamSendResult, error) {
|
||||
session := findSession(event.MessageID, event.Account)
|
||||
if session == nil {
|
||||
return DownstreamSendResult{}, nil
|
||||
return DownstreamSendResult{
|
||||
Retryable: true,
|
||||
ReasonCode: "CLIENT_DISCONNECTED",
|
||||
ErrorMessage: "下游客户端当前未连接,等待自动重试",
|
||||
}, nil
|
||||
}
|
||||
content, err := cmpputils.Utf8ToUcs2(event.Content)
|
||||
if err != nil {
|
||||
@@ -952,6 +994,18 @@ func PushUplinkWithResult(event DownstreamUplink) (DownstreamSendResult, error)
|
||||
return sendDownstream(session, deliver, event.DeliveryID)
|
||||
}
|
||||
|
||||
func errorMessageWithCode(message string, code string) string {
|
||||
message = strings.TrimSpace(message)
|
||||
code = strings.TrimSpace(code)
|
||||
if message == "" {
|
||||
message = "gateway did not complete downstream delivery"
|
||||
}
|
||||
if code == "" {
|
||||
return message
|
||||
}
|
||||
return fmt.Sprintf("%s (%s)", message, code)
|
||||
}
|
||||
|
||||
func downstreamDeliverPacket(session *downstreamSession, messageID uint64, destID string, sourceTerminalID string, msgFmt uint8, registerDelivery uint8, content string) cmpp.Packer {
|
||||
if session != nil && (session.protocol == "cmpp20" || session.protocol == "cmpp21") {
|
||||
return &cmpp.Cmpp2DeliverReqPkt{
|
||||
|
||||
@@ -302,6 +302,43 @@ func TestSubmitResponsePrecedesQueuedFailureReceipt(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiptWithoutOriginalSequenceIsUnrecoverable(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
result, err := PushReceiptWithResult(DownstreamReceipt{
|
||||
DeliveryID: "delivery-history",
|
||||
Account: "100001",
|
||||
MessageID: "MSG-HISTORY",
|
||||
ReceiptStatus: "undelivered",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("push receipt: %v", err)
|
||||
}
|
||||
if result.Sent || result.Retryable || result.ReasonCode != "MISSING_SUBMIT_SEQUENCE_ID" {
|
||||
t.Fatalf("unexpected result: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecoverableReceiptWaitsForClientConnection(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
|
||||
result, err := PushReceiptWithResult(DownstreamReceipt{
|
||||
DeliveryID: "delivery-retry",
|
||||
Account: "100001",
|
||||
MessageID: "MSG-RETRY",
|
||||
SubmitSequenceID: 77,
|
||||
ReceiptStatus: "delivered",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("push receipt: %v", err)
|
||||
}
|
||||
if result.Sent || !result.Retryable || result.ReasonCode != "CLIENT_DISCONNECTED" {
|
||||
t.Fatalf("unexpected result: %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundServerNegotiatesCMPP2AndUsesAuthenticatedAccountForSubmit(t *testing.T) {
|
||||
resetDownstreamRegistry()
|
||||
defer resetDownstreamRegistry()
|
||||
@@ -571,8 +608,10 @@ func TestRecoverPendingCandidatesWritesWaitingConnectionStatus(t *testing.T) {
|
||||
_ = json.NewEncoder(w).Encode([]pendingDelivery{{
|
||||
ID: "delivery-1",
|
||||
DeliveryType: "receipt",
|
||||
Payload: json.RawMessage(`{"messageId":"MSG-404","phoneNumber":"13800000001","receiptStatus":"delivered"}`),
|
||||
Payload: json.RawMessage(`{"account":"100010","messageId":"MSG-404","submitSequenceId":77,"phoneNumber":"13800000001","receiptStatus":"delivered"}`),
|
||||
}})
|
||||
case "/api/gateway/events/downstream/failed":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
case "/api/gateway/events/downstream/recovery-status":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user