变更: - 创建 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
20 KiB
20 KiB
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错误,建议:
- 使用指数退避重试策略
- 减少并发请求数
- 联系管理员增加配额
性能优化建议
- 批量处理:合理控制并发数(建议2-4个并发)
- 图片预处理:压缩大图片后再上传
- 复用连接:使用HTTP keep-alive
- 错误重试:实现指数退避策略
- 超时设置:根据图片大小设置合理超时时间
支持
如有问题,请访问:
- 文档:
http://localhost:8080/docs - GitHub:https://github.com/let5sne/IOPaint/issues