package shunyunbao import ( "context" "mime" "net/http" "net/url" "strconv" "strings" "cmroubao/backend-api/internal/domain" "cmroubao/backend-api/internal/usecase" ) const ( ProductImagePath = "/api/p/file" maxProductImageBytes = 20 << 20 ) func (manager *SessionManager) FetchProductImage( ctx context.Context, productThumbRef string, ) (usecase.FreightSourceImage, error) { productThumbRef = strings.TrimSpace(productThumbRef) value, err := strconv.ParseUint(productThumbRef, 10, 64) if err != nil || value == 0 || strconv.FormatUint(value, 10) != productThumbRef { return usecase.FreightSourceImage{}, domain.ErrFreightImageInvalid } manager.mu.Lock() if !manager.configuredLocked() { manager.mu.Unlock() return usecase.FreightSourceImage{}, domain.ErrFreightSourceNotConfigured } if !manager.authenticated { manager.mu.Unlock() return usecase.FreightSourceImage{}, domain.ErrFreightSourceSessionNeeded } baseURL := manager.baseURL headers := manager.headers.Clone() client := manager.http manager.mu.Unlock() query := url.Values{} query.Set("id", productThumbRef) request, err := http.NewRequestWithContext( ctx, http.MethodGet, baseURL+ProductImagePath+"?"+query.Encode(), nil, ) if err != nil { return usecase.FreightSourceImage{}, domain.ErrFreightSourceUnavailable } for name, values := range headers { request.Header[name] = append([]string(nil), values...) } request.Header.Set("Accept", "image/jpeg, image/png, image/webp") manager.logERPRequest(request) response, err := client.Do(request) if err != nil { manager.logERPTransportFailure(request) return usecase.FreightSourceImage{}, domain.ErrFreightSourceUnavailable } if response.StatusCode < http.StatusOK || response.StatusCode >= http.StatusMultipleChoices { defer response.Body.Close() manager.logERPBinaryResponse(request, response) switch response.StatusCode { case http.StatusUnauthorized, http.StatusForbidden: manager.mu.Lock() manager.clearAuthenticatedLocked() manager.mu.Unlock() return usecase.FreightSourceImage{}, domain.ErrFreightSourceSessionNeeded case http.StatusNotFound: return usecase.FreightSourceImage{}, domain.ErrFreightImageNotFound default: return usecase.FreightSourceImage{}, domain.ErrFreightSourceUnavailable } } mediaType, _, err := mime.ParseMediaType( response.Header.Get("Content-Type"), ) if err != nil || !supportedProductImageType(mediaType) || response.ContentLength > maxProductImageBytes { response.Body.Close() manager.logERPBinaryResponse(request, response) return usecase.FreightSourceImage{}, domain.ErrFreightImageInvalid } manager.logERPBinaryResponse(request, response) return usecase.FreightSourceImage{ Content: response.Body, MediaType: mediaType, }, nil } func supportedProductImageType(value string) bool { switch strings.ToLower(value) { case "image/jpeg", "image/png", "image/webp": return true default: return false } } func (manager *SessionManager) logERPBinaryResponse( request *http.Request, response *http.Response, ) { if !manager.diagnosticsOn { return } byteCount := "unknown" if response.ContentLength >= 0 { byteCount = strconv.FormatInt(response.ContentLength, 10) } manager.diagnosticLog( "erp_response method=" + request.Method + " path=" + request.URL.EscapedPath() + " status=" + strconv.Itoa(response.StatusCode) + " content_type=" + diagnosticContentType(response.Header.Get("Content-Type")) + " bytes=" + byteCount + " body=omitted_binary", ) } var _ usecase.FreightImageSource = (*SessionManager)(nil)