package service import ( "bytes" "context" "fmt" "io" "mime/multipart" "net/http" "strings" "time" ) // SegmentHTTPClient 自部署抠图 HTTP 服务(如 rembg s → POST /api/remove) type SegmentHTTPClient struct { endpoint string client *http.Client } func NewSegmentHTTPClient(endpoint string) *SegmentHTTPClient { return &SegmentHTTPClient{ endpoint: strings.TrimSpace(endpoint), client: &http.Client{Timeout: 120 * time.Second}, } } func (c *SegmentHTTPClient) enabled() bool { return c.endpoint != "" } // RemoveBackground 上传图片字节,返回透明 PNG func (c *SegmentHTTPClient) RemoveBackground(ctx context.Context, imageData []byte, contentType string) ([]byte, error) { if !c.enabled() { return nil, fmt.Errorf("抠图 HTTP 地址未配置") } if len(imageData) == 0 { return nil, fmt.Errorf("图片为空") } ext := "jpg" if strings.Contains(strings.ToLower(contentType), "png") { ext = "png" } var buf bytes.Buffer w := multipart.NewWriter(&buf) part, err := w.CreateFormFile("image", "upload."+ext) if err != nil { return nil, err } if _, err := part.Write(imageData); err != nil { return nil, err } _ = w.WriteField("scene", "portrait") if err := w.Close(); err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint, &buf) if err != nil { return nil, err } req.Header.Set("Content-Type", w.FormDataContentType()) res, err := c.client.Do(req) if err != nil { return nil, err } defer res.Body.Close() raw, err := io.ReadAll(io.LimitReader(res.Body, maxSegmentImageBytes*4)) if err != nil { return nil, err } if res.StatusCode < 200 || res.StatusCode >= 300 { return nil, fmt.Errorf("抠图 HTTP %d: %s", res.StatusCode, truncate(string(raw), 240)) } ct := strings.ToLower(res.Header.Get("Content-Type")) if strings.Contains(ct, "image/") { return raw, nil } return nil, fmt.Errorf("抠图 HTTP 返回非图片内容") }