diff --git a/backend/internal/handler/base_url.go b/backend/internal/handler/base_url.go new file mode 100644 index 0000000..1189db0 --- /dev/null +++ b/backend/internal/handler/base_url.go @@ -0,0 +1,18 @@ +package handler + +import ( + "strings" + + "github.com/gin-gonic/gin" +) + +// requestBaseURL 根据当前请求推导站点基础地址(如 https://skin.khhao.com)。 +// 优先取配置的 DELIVERY_BASE_URL(服务层),为空时由请求的 scheme + Host 推导, +// 保证发货链接始终返回完整可访问的 URL。 +func requestBaseURL(c *gin.Context) string { + scheme := "http" + if c.Request.TLS != nil || strings.EqualFold(c.GetHeader("X-Forwarded-Proto"), "https") { + scheme = "https" + } + return scheme + "://" + c.Request.Host +} diff --git a/backend/internal/handler/merchant.go b/backend/internal/handler/merchant.go index 5ee2659..fd09359 100644 --- a/backend/internal/handler/merchant.go +++ b/backend/internal/handler/merchant.go @@ -168,7 +168,7 @@ func (h *MerchantHandler) CreateTestOrder(c *gin.Context) { } func (h *MerchantHandler) GetDeliveryLink(c *gin.Context) { - link, err := h.deliverySvc.GetOrCreateDeliveryLink(middleware.GetMerchantID(c), c.Param("order_no")) + link, err := h.deliverySvc.GetOrCreateDeliveryLink(middleware.GetMerchantID(c), c.Param("order_no"), requestBaseURL(c)) if err != nil { writeDeliveryError(c, err) return @@ -185,7 +185,7 @@ func (h *MerchantHandler) RevokeDeliveryLink(c *gin.Context) { } func (h *MerchantHandler) RestoreDeliveryLink(c *gin.Context) { - link, err := h.deliverySvc.RestoreDeliveryLink(middleware.GetMerchantID(c), c.Param("order_no")) + link, err := h.deliverySvc.RestoreDeliveryLink(middleware.GetMerchantID(c), c.Param("order_no"), requestBaseURL(c)) if err != nil { writeDeliveryError(c, err) return diff --git a/backend/internal/handler/open_v1.go b/backend/internal/handler/open_v1.go index 8d83605..3942f6b 100644 --- a/backend/internal/handler/open_v1.go +++ b/backend/internal/handler/open_v1.go @@ -163,7 +163,7 @@ func (h *OpenV1Handler) GetDeliveryLink(c *gin.Context) { openlog.SetAction(c, openlog.ActionQuery) orderNo := c.Param("order_no") openlog.Info(c, "delivery_link start order_no=%s", orderNo) - link, err := h.deliverySvc.GetOrCreateDeliveryLink(middleware.GetMerchantID(c), orderNo) + link, err := h.deliverySvc.GetOrCreateDeliveryLink(middleware.GetMerchantID(c), orderNo, requestBaseURL(c)) if err != nil { openlog.Warn(c, "delivery_link fail order_no=%s err=%v", orderNo, err) if sc, ok := err.(interface{ HTTPStatus() int }); ok { diff --git a/backend/internal/service/delivery.go b/backend/internal/service/delivery.go index 2ef7ada..355ffd7 100644 --- a/backend/internal/service/delivery.go +++ b/backend/internal/service/delivery.go @@ -665,7 +665,7 @@ func optionalUint(value uint) *uint { return &out } -func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo string) (*DeliveryLinkResult, error) { +func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo, requestBaseURL string) (*DeliveryLinkResult, error) { orderNo = strings.TrimSpace(orderNo) if orderNo == "" { return nil, errors.New("订单号不能为空") @@ -679,7 +679,7 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo strin return nil, newDeliveryHTTPError(http.StatusForbidden, "发货链接已作废") } if order.DeliveryLinkExpiresAt != nil && order.DeliveryLinkExpiresAt.After(now) { - return s.buildDeliveryLinkResult(order.OrderNo, order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second)), nil + return s.buildDeliveryLinkResult(order.OrderNo, order.DeliveryLinkExpiresAt.UTC().Truncate(time.Second), requestBaseURL), nil } canShip, reason := CanFulfill(order) if !canShip { @@ -697,7 +697,7 @@ func (s *DeliveryService) GetOrCreateDeliveryLink(merchantID uint, orderNo strin }).Error; err != nil { return nil, err } - return s.buildDeliveryLinkResult(order.OrderNo, expiresAt), nil + return s.buildDeliveryLinkResult(order.OrderNo, expiresAt, requestBaseURL), nil } func (s *DeliveryService) RevokeDeliveryLink(merchantID uint, orderNo string) error { @@ -717,7 +717,7 @@ func (s *DeliveryService) RevokeDeliveryLink(merchantID uint, orderNo string) er }).Error } -func (s *DeliveryService) RestoreDeliveryLink(merchantID uint, orderNo string) (*DeliveryLinkResult, error) { +func (s *DeliveryService) RestoreDeliveryLink(merchantID uint, orderNo, requestBaseURL string) (*DeliveryLinkResult, error) { orderNo = strings.TrimSpace(orderNo) if orderNo == "" { return nil, errors.New("订单号不能为空") @@ -745,7 +745,7 @@ func (s *DeliveryService) RestoreDeliveryLink(merchantID uint, orderNo string) ( }).Error; err != nil { return nil, err } - return s.buildDeliveryLinkResult(order.OrderNo, expiresAt), nil + return s.buildDeliveryLinkResult(order.OrderNo, expiresAt, requestBaseURL), nil } func (s *DeliveryService) prepareOrder(orderNo string, requireCanShip bool, auth *DeliveryLinkAuth) (*DeliveryOrderInfo, *model.FulfillmentOrder, string, error) { @@ -842,25 +842,29 @@ func (s *DeliveryService) authorizeDeliveryLink(order *model.FulfillmentOrder, a return nil } -func (s *DeliveryService) buildDeliveryLinkResult(orderNo string, expiresAt time.Time) *DeliveryLinkResult { +func (s *DeliveryService) buildDeliveryLinkResult(orderNo string, expiresAt time.Time, requestBaseURL string) *DeliveryLinkResult { expiresAt = expiresAt.UTC() exp := expiresAt.Unix() sign := s.signDeliveryLink(orderNo, exp) return &DeliveryLinkResult{ OrderNo: orderNo, - DeliveryURL: s.buildDeliveryURL(orderNo, exp, sign), + DeliveryURL: s.buildDeliveryURL(orderNo, exp, sign, requestBaseURL), ExpiresAt: expiresAt, Exp: exp, Sign: sign, } } -func (s *DeliveryService) buildDeliveryURL(orderNo string, exp int64, sign string) string { +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)) - if s.linkBaseURL == "" { + base := strings.TrimRight(s.linkBaseURL, "/") + if base == "" { + base = strings.TrimRight(requestBaseURL, "/") + } + if base == "" { return path } - return s.linkBaseURL + path + return base + path } func (s *DeliveryService) signDeliveryLink(orderNo string, exp int64) string { diff --git a/backend/internal/service/delivery_test.go b/backend/internal/service/delivery_test.go index 682ae5d..f40d4c0 100644 --- a/backend/internal/service/delivery_test.go +++ b/backend/internal/service/delivery_test.go @@ -29,7 +29,7 @@ func TestDeliveryLinkGenerateAuthorizeAndRevoke(t *testing.T) { t.Fatalf("create order: %v", err) } - link, err := deliverySvc.GetOrCreateDeliveryLink(merchantID, created.Order.OrderNo) + link, err := deliverySvc.GetOrCreateDeliveryLink(merchantID, created.Order.OrderNo, "https://shop.example") if err != nil { t.Fatalf("get delivery link: %v", err) } @@ -61,7 +61,7 @@ func TestDeliveryLinkGenerateAuthorizeAndRevoke(t *testing.T) { t.Fatalf("delivery_link_revoked_at should be stored") } - restored, err := deliverySvc.RestoreDeliveryLink(merchantID, created.Order.OrderNo) + restored, err := deliverySvc.RestoreDeliveryLink(merchantID, created.Order.OrderNo, "https://shop.example") if err != nil { t.Fatalf("restore delivery link: %v", err) }