package service import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt" "net/http" "net/url" "strconv" "strings" "time" "affiliate_dash/internal/model" ) func (s *DeliveryService) authorizeDeliveryLink(order *model.FulfillmentOrder, auth DeliveryLinkAuth) error { if order == nil { return newDeliveryHTTPError(http.StatusNotFound, "订单不存在") } if strings.TrimSpace(auth.Sign) == "" { return newDeliveryHTTPError(http.StatusForbidden, "链接参数缺失") } if order.DeliveryLinkRevokedAt != nil { return newDeliveryHTTPError(http.StatusForbidden, "发货链接已作废") } // 订单已取消后链接立即失效,防止已退款订单仍通过链接查看或继续发货。 if normalizeOrderStatus(order) == model.OrderStatusCancelled { return newDeliveryHTTPError(http.StatusForbidden, "订单已取消,发货链接不可用") } // 从未生成过链接(兼容历史数据:本次改动前生成的有时效链接没有 generated_at 记录)。 if order.DeliveryLinkGeneratedAt == nil && order.DeliveryLinkExpiresAt == nil { return newDeliveryHTTPError(http.StatusForbidden, "发货链接未生成") } // 永久有效链接(expires_at 为 NULL):仅校验签名(exp 固定为 0),不检查过期。 if order.DeliveryLinkExpiresAt == nil { expected := s.signDeliveryLink(order.OrderNo, auth.Exp) if !hmac.Equal([]byte(strings.ToLower(strings.TrimSpace(auth.Sign))), []byte(expected)) { return newDeliveryHTTPError(http.StatusForbidden, "发货链接签名无效") } return nil } expiresAt := order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second) linkExpires := time.Unix(auth.Exp, 0).UTC() if !linkExpires.Equal(expiresAt) { return newDeliveryHTTPError(http.StatusForbidden, "发货链接已失效") } if time.Now().UTC().After(linkExpires) { return newDeliveryHTTPError(http.StatusForbidden, "发货链接已过期") } expected := s.signDeliveryLink(order.OrderNo, auth.Exp) if !hmac.Equal([]byte(strings.ToLower(strings.TrimSpace(auth.Sign))), []byte(expected)) { return newDeliveryHTTPError(http.StatusForbidden, "发货链接签名无效") } return nil } // buildDeliveryLinkResult 组装链接结果;expiresAt 为 nil 表示永久有效(exp 固定为 0)。 func (s *DeliveryService) buildDeliveryLinkResult(orderNo string, expiresAt *time.Time, requestBaseURL string) *DeliveryLinkResult { exp := int64(0) if expiresAt != nil { exp = expiresAt.UTC().Unix() } sign := s.signDeliveryLink(orderNo, exp) return &DeliveryLinkResult{ OrderNo: orderNo, DeliveryURL: s.buildDeliveryURL(orderNo, exp, sign, requestBaseURL), ExpiresAt: expiresAt, Exp: exp, Sign: sign, } } func (s *DeliveryService) buildDeliveryURL(orderNo string, exp int64, sign, requestBaseURL string) string { path := fmt.Sprintf("/delivery/%s/%s?exp=%d&sign=%s", url.PathEscape(s.channel), url.PathEscape(orderNo), exp, url.QueryEscape(sign)) base := strings.TrimRight(s.linkBaseURL, "/") if base == "" { base = strings.TrimRight(requestBaseURL, "/") } if base == "" { return path } return base + path } func (s *DeliveryService) signDeliveryLink(orderNo string, exp int64) string { mac := hmac.New(sha256.New, []byte(s.linkSecret)) _, _ = mac.Write([]byte(orderNo + "|" + strconv.FormatInt(exp, 10))) return hex.EncodeToString(mac.Sum(nil)) }