Files
IOPaint/docs/API_CLIENT_EXAMPLES.md
let5sne b6ac3f022a 📁 重组文档目录结构
变更:
- 创建 docs/ 目录统一管理所有文档
- 移动所有 API 文档到 docs/ 目录
  - API_DOCS_INDEX.md
  - RESTFUL_API_DOCUMENTATION.md
  - API_SERVICE_README.md
  - API_CLIENT_EXAMPLES.md
  - API_SERVICE_GUIDE.md
  - BRANCH_README.md
  - openapi.yaml
  - IOPaint_API.postman_collection.json
  - UPGRADE_NOTES.md

- 更新所有文档间的引用路径
- 更新 README.md 中的文档链接
- 创建 docs/README.md 作为文档入口

优势:
 清晰的目录结构
 文档集中管理
 易于查找和维护
 符合项目规范

🔧 Generated with Claude Code
2025-11-28 18:04:26 +00:00

20 KiB
Raw Blame History

IOPaint API 客户端示例

本文档提供多种编程语言的API调用示例。

目录


配置信息

API_URL=http://localhost:8080
API_KEY=your_secret_key_change_me

Python

基础示例

import requests

def remove_watermark(image_path, mask_path=None, api_key="your_secret_key_change_me"):
    """去除图片水印"""
    url = "http://localhost:8080/api/v1/remove-watermark"
    headers = {"X-API-Key": api_key}

    files = {
        "image": open(image_path, "rb")
    }

    if mask_path:
        files["mask"] = open(mask_path, "rb")

    response = requests.post(url, headers=headers, files=files)

    if response.status_code == 200:
        # 保存结果
        output_path = "result.png"
        with open(output_path, "wb") as f:
            f.write(response.content)
        print(f"✓ 处理成功!结果已保存到: {output_path}")
        print(f"处理时间: {response.headers.get('X-Processing-Time')}秒")
        return output_path
    else:
        print(f"✗ 处理失败: {response.status_code}")
        print(response.json())
        return None

# 使用示例
remove_watermark("input.jpg")

高级示例(含错误处理和重试)

import requests
import time
from pathlib import Path

class IOPaintClient:
    """IOPaint API客户端"""

    def __init__(self, api_url="http://localhost:8080", api_key=None):
        self.api_url = api_url.rstrip("/")
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({"X-API-Key": api_key})

    def health_check(self):
        """健康检查"""
        response = self.session.get(f"{self.api_url}/api/v1/health")
        return response.json()

    def get_stats(self):
        """获取使用统计"""
        response = self.session.get(f"{self.api_url}/api/v1/stats")
        return response.json()

    def remove_watermark(
        self,
        image_path,
        mask_path=None,
        output_path=None,
        max_retries=3,
        timeout=120
    ):
        """
        去除图片水印

        参数:
            image_path: 输入图片路径
            mask_path: 遮罩图片路径(可选)
            output_path: 输出路径可选默认为input_result.png
            max_retries: 最大重试次数
            timeout: 超时时间(秒)

        返回:
            成功返回输出路径失败返回None
        """
        # 准备文件
        files = {"image": open(image_path, "rb")}
        if mask_path:
            files["mask"] = open(mask_path, "rb")

        # 确定输出路径
        if output_path is None:
            input_path = Path(image_path)
            output_path = input_path.parent / f"{input_path.stem}_result.png"

        # 重试逻辑
        for attempt in range(max_retries):
            try:
                response = self.session.post(
                    f"{self.api_url}/api/v1/remove-watermark",
                    files=files,
                    timeout=timeout
                )

                if response.status_code == 200:
                    # 保存结果
                    with open(output_path, "wb") as f:
                        f.write(response.content)

                    print(f"✓ 处理成功!")
                    print(f"  输出: {output_path}")
                    print(f"  处理时间: {response.headers.get('X-Processing-Time')}秒")
                    print(f"  图片尺寸: {response.headers.get('X-Image-Size')}")
                    return str(output_path)

                elif response.status_code == 429:
                    # 限流,等待后重试
                    wait_time = 2 ** attempt
                    print(f"⚠ 请求过于频繁,等待{wait_time}秒后重试...")
                    time.sleep(wait_time)
                    continue

                else:
                    # 其他错误
                    print(f"✗ 处理失败 ({response.status_code})")
                    error_data = response.json()
                    print(f"  错误: {error_data.get('detail', '未知错误')}")
                    return None

            except requests.Timeout:
                print(f"⚠ 请求超时 (尝试 {attempt + 1}/{max_retries})")
                if attempt < max_retries - 1:
                    continue
                else:
                    print("✗ 超过最大重试次数")
                    return None

            except Exception as e:
                print(f"✗ 发生错误: {e}")
                return None

            finally:
                # 关闭文件
                for f in files.values():
                    if hasattr(f, 'close'):
                        f.close()

        return None

    def batch_process(self, image_dir, output_dir=None, mask_dir=None):
        """
        批量处理图片

        参数:
            image_dir: 输入图片目录
            output_dir: 输出目录(可选)
            mask_dir: 遮罩目录(可选,按文件名匹配)
        """
        image_dir = Path(image_dir)
        if output_dir:
            output_dir = Path(output_dir)
            output_dir.mkdir(exist_ok=True, parents=True)

        # 支持的图片格式
        image_exts = {".jpg", ".jpeg", ".png", ".webp"}
        images = [
            f for f in image_dir.iterdir()
            if f.suffix.lower() in image_exts
        ]

        print(f"找到 {len(images)} 张图片")

        results = {"success": 0, "failed": 0}
        for i, image_path in enumerate(images, 1):
            print(f"\n[{i}/{len(images)}] 处理: {image_path.name}")

            # 查找对应的遮罩
            mask_path = None
            if mask_dir:
                mask_path = Path(mask_dir) / image_path.name
                if not mask_path.exists():
                    mask_path = None

            # 确定输出路径
            if output_dir:
                out_path = output_dir / f"{image_path.stem}_result.png"
            else:
                out_path = image_path.parent / f"{image_path.stem}_result.png"

            # 处理
            result = self.remove_watermark(image_path, mask_path, out_path)
            if result:
                results["success"] += 1
            else:
                results["failed"] += 1

        # 总结
        print("\n" + "=" * 60)
        print(f"批量处理完成!")
        print(f"  成功: {results['success']}")
        print(f"  失败: {results['failed']}")
        print("=" * 60)

# 使用示例
if __name__ == "__main__":
    # 创建客户端
    client = IOPaintClient(
        api_url="http://localhost:8080",
        api_key="your_secret_key_change_me"
    )

    # 健康检查
    print("健康检查:", client.health_check())

    # 单张图片处理
    client.remove_watermark("test.jpg")

    # 批量处理
    client.batch_process("./input_images", "./output_images")

JavaScript/Node.js

使用 axios

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

async function removeWatermark(imagePath, maskPath = null, apiKey = 'your_secret_key_change_me') {
    const url = 'http://localhost:8080/api/v1/remove-watermark';

    const formData = new FormData();
    formData.append('image', fs.createReadStream(imagePath));

    if (maskPath) {
        formData.append('mask', fs.createReadStream(maskPath));
    }

    try {
        const response = await axios.post(url, formData, {
            headers: {
                'X-API-Key': apiKey,
                ...formData.getHeaders()
            },
            responseType: 'arraybuffer',
            timeout: 120000  // 120秒超时
        });

        // 保存结果
        const outputPath = 'result.png';
        fs.writeFileSync(outputPath, response.data);

        console.log('✓ 处理成功!');
        console.log(`  输出: ${outputPath}`);
        console.log(`  处理时间: ${response.headers['x-processing-time']}秒`);

        return outputPath;

    } catch (error) {
        if (error.response) {
            console.error('✗ 处理失败:', error.response.status);
            console.error('  错误:', error.response.data.toString());
        } else {
            console.error('✗ 请求失败:', error.message);
        }
        return null;
    }
}

// 使用示例
removeWatermark('input.jpg');

完整客户端类

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');

class IOPaintClient {
    constructor(apiUrl = 'http://localhost:8080', apiKey = null) {
        this.apiUrl = apiUrl.replace(/\/$/, '');
        this.apiKey = apiKey;
        this.client = axios.create({
            baseURL: this.apiUrl,
            headers: {
                'X-API-Key': apiKey
            },
            timeout: 120000
        });
    }

    async healthCheck() {
        const response = await this.client.get('/api/v1/health');
        return response.data;
    }

    async getStats() {
        const response = await this.client.get('/api/v1/stats');
        return response.data;
    }

    async removeWatermark(imagePath, maskPath = null, outputPath = null) {
        const formData = new FormData();
        formData.append('image', fs.createReadStream(imagePath));

        if (maskPath) {
            formData.append('mask', fs.createReadStream(maskPath));
        }

        // 确定输出路径
        if (!outputPath) {
            const parsed = path.parse(imagePath);
            outputPath = path.join(parsed.dir, `${parsed.name}_result.png`);
        }

        try {
            const response = await this.client.post('/api/v1/remove-watermark', formData, {
                headers: formData.getHeaders(),
                responseType: 'arraybuffer'
            });

            fs.writeFileSync(outputPath, response.data);

            console.log('✓ 处理成功!');
            console.log(`  输出: ${outputPath}`);
            console.log(`  处理时间: ${response.headers['x-processing-time']}秒`);

            return outputPath;

        } catch (error) {
            if (error.response) {
                console.error('✗ 处理失败:', error.response.status);
            } else {
                console.error('✗ 请求失败:', error.message);
            }
            return null;
        }
    }
}

// 使用示例
(async () => {
    const client = new IOPaintClient('http://localhost:8080', 'your_secret_key_change_me');

    // 健康检查
    const health = await client.healthCheck();
    console.log('健康检查:', health);

    // 处理图片
    await client.removeWatermark('test.jpg');
})();

cURL

基础使用

# 简单调用
curl -X POST http://localhost:8080/api/v1/remove-watermark \
  -H "X-API-Key: your_secret_key_change_me" \
  -F "image=@input.jpg" \
  -o result.png

# 带遮罩
curl -X POST http://localhost:8080/api/v1/remove-watermark \
  -H "X-API-Key: your_secret_key_change_me" \
  -F "image=@input.jpg" \
  -F "mask=@mask.png" \
  -o result.png

# 显示详细信息
curl -X POST http://localhost:8080/api/v1/remove-watermark \
  -H "X-API-Key: your_secret_key_change_me" \
  -F "image=@input.jpg" \
  -o result.png \
  -v

# 健康检查
curl http://localhost:8080/api/v1/health

Bash脚本批量处理

#!/bin/bash

API_URL="http://localhost:8080/api/v1/remove-watermark"
API_KEY="your_secret_key_change_me"
INPUT_DIR="./input"
OUTPUT_DIR="./output"

mkdir -p "$OUTPUT_DIR"

for image in "$INPUT_DIR"/*.{jpg,jpeg,png}; do
    [ -f "$image" ] || continue

    filename=$(basename "$image")
    name="${filename%.*}"
    output="$OUTPUT_DIR/${name}_result.png"

    echo "处理: $filename"

    curl -X POST "$API_URL" \
        -H "X-API-Key: $API_KEY" \
        -F "image=@$image" \
        -o "$output" \
        -s -w "状态码: %{http_code}, 时间: %{time_total}s\n"
done

echo "批量处理完成!"

PHP

<?php

class IOPaintClient {
    private $apiUrl;
    private $apiKey;

    public function __construct($apiUrl = 'http://localhost:8080', $apiKey = null) {
        $this->apiUrl = rtrim($apiUrl, '/');
        $this->apiKey = $apiKey;
    }

    public function healthCheck() {
        $ch = curl_init($this->apiUrl . '/api/v1/health');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $response = curl_exec($ch);
        curl_close($ch);
        return json_decode($response, true);
    }

    public function removeWatermark($imagePath, $maskPath = null, $outputPath = null) {
        $url = $this->apiUrl . '/api/v1/remove-watermark';

        // 准备文件
        $postData = [
            'image' => new CURLFile($imagePath)
        ];

        if ($maskPath) {
            $postData['mask'] = new CURLFile($maskPath);
        }

        // 确定输出路径
        if (!$outputPath) {
            $pathInfo = pathinfo($imagePath);
            $outputPath = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_result.png';
        }

        // 发送请求
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-API-Key: ' . $this->apiKey
        ]);
        curl_setopt($ch, CURLOPT_TIMEOUT, 120);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode == 200) {
            file_put_contents($outputPath, $response);
            echo "✓ 处理成功!输出: $outputPath\n";
            return $outputPath;
        } else {
            echo "✗ 处理失败 (HTTP $httpCode)\n";
            return null;
        }
    }
}

// 使用示例
$client = new IOPaintClient('http://localhost:8080', 'your_secret_key_change_me');

// 健康检查
print_r($client->healthCheck());

// 处理图片
$client->removeWatermark('test.jpg');
?>

Java

import okhttp3.*;
import java.io.*;
import java.nio.file.*;

public class IOPaintClient {
    private final String apiUrl;
    private final String apiKey;
    private final OkHttpClient client;

    public IOPaintClient(String apiUrl, String apiKey) {
        this.apiUrl = apiUrl.replaceAll("/$", "");
        this.apiKey = apiKey;
        this.client = new OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .build();
    }

    public String removeWatermark(String imagePath, String maskPath, String outputPath) throws IOException {
        // 构建请求
        MultipartBody.Builder builder = new MultipartBody.Builder()
            .setType(MultipartBody.FORM)
            .addFormDataPart("image", "image.jpg",
                RequestBody.create(new File(imagePath), MediaType.parse("image/*")));

        if (maskPath != null) {
            builder.addFormDataPart("mask", "mask.png",
                RequestBody.create(new File(maskPath), MediaType.parse("image/*")));
        }

        RequestBody requestBody = builder.build();

        Request request = new Request.Builder()
            .url(apiUrl + "/api/v1/remove-watermark")
            .addHeader("X-API-Key", apiKey)
            .post(requestBody)
            .build();

        // 发送请求
        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                // 保存结果
                if (outputPath == null) {
                    Path path = Paths.get(imagePath);
                    String name = path.getFileName().toString();
                    name = name.substring(0, name.lastIndexOf('.'));
                    outputPath = path.getParent().resolve(name + "_result.png").toString();
                }

                try (InputStream is = response.body().byteStream();
                     FileOutputStream fos = new FileOutputStream(outputPath)) {
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = is.read(buffer)) != -1) {
                        fos.write(buffer, 0, bytesRead);
                    }
                }

                System.out.println("✓ 处理成功!输出: " + outputPath);
                return outputPath;
            } else {
                System.err.println("✗ 处理失败: " + response.code());
                System.err.println(response.body().string());
                return null;
            }
        }
    }

    public static void main(String[] args) {
        IOPaintClient client = new IOPaintClient(
            "http://localhost:8080",
            "your_secret_key_change_me"
        );

        try {
            client.removeWatermark("test.jpg", null, null);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Go

package main

import (
    "bytes"
    "fmt"
    "io"
    "mime/multipart"
    "net/http"
    "os"
    "path/filepath"
    "time"
)

type IOPaintClient struct {
    apiURL string
    apiKey string
    client *http.Client
}

func NewIOPaintClient(apiURL, apiKey string) *IOPaintClient {
    return &IOPaintClient{
        apiURL: apiURL,
        apiKey: apiKey,
        client: &http.Client{
            Timeout: 120 * time.Second,
        },
    }
}

func (c *IOPaintClient) RemoveWatermark(imagePath, maskPath, outputPath string) error {
    // 准备multipart请求
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // 添加图片
    imageFile, err := os.Open(imagePath)
    if err != nil {
        return err
    }
    defer imageFile.Close()

    imagePart, err := writer.CreateFormFile("image", filepath.Base(imagePath))
    if err != nil {
        return err
    }
    io.Copy(imagePart, imageFile)

    // 添加遮罩(如果有)
    if maskPath != "" {
        maskFile, err := os.Open(maskPath)
        if err != nil {
            return err
        }
        defer maskFile.Close()

        maskPart, err := writer.CreateFormFile("mask", filepath.Base(maskPath))
        if err != nil {
            return err
        }
        io.Copy(maskPart, maskFile)
    }

    writer.Close()

    // 创建请求
    req, err := http.NewRequest("POST", c.apiURL+"/api/v1/remove-watermark", body)
    if err != nil {
        return err
    }

    req.Header.Set("X-API-Key", c.apiKey)
    req.Header.Set("Content-Type", writer.FormDataContentType())

    // 发送请求
    resp, err := c.client.Do(req)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    // 检查响应
    if resp.StatusCode != 200 {
        return fmt.Errorf("请求失败: %d", resp.StatusCode)
    }

    // 确定输出路径
    if outputPath == "" {
        ext := filepath.Ext(imagePath)
        name := imagePath[:len(imagePath)-len(ext)]
        outputPath = name + "_result.png"
    }

    // 保存结果
    outFile, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer outFile.Close()

    _, err = io.Copy(outFile, resp.Body)
    if err != nil {
        return err
    }

    fmt.Printf("✓ 处理成功!输出: %s\n", outputPath)
    fmt.Printf("  处理时间: %s秒\n", resp.Header.Get("X-Processing-Time"))

    return nil
}

func main() {
    client := NewIOPaintClient("http://localhost:8080", "your_secret_key_change_me")

    err := client.RemoveWatermark("test.jpg", "", "")
    if err != nil {
        fmt.Printf("错误: %v\n", err)
    }
}

错误处理

常见错误代码

状态码 错误 解决方案
401 未授权 检查API Key是否正确
400 请求错误 检查图片格式、大小是否符合要求
429 请求过多 降低请求频率,稍后重试
500 服务器错误 检查服务器日志,联系技术支持
503 服务不可用 服务器可能正在重启,稍后重试

限流说明

默认限流每秒10个请求突发20个请求

如果遇到429错误建议

  1. 使用指数退避重试策略
  2. 减少并发请求数
  3. 联系管理员增加配额

性能优化建议

  1. 批量处理合理控制并发数建议2-4个并发
  2. 图片预处理:压缩大图片后再上传
  3. 复用连接使用HTTP keep-alive
  4. 错误重试:实现指数退避策略
  5. 超时设置:根据图片大小设置合理超时时间

支持

如有问题,请访问: