diff --git a/Real-ESRGAN_API_文档.md b/Real-ESRGAN_API_文档.md new file mode 100644 index 0000000..52eacd3 --- /dev/null +++ b/Real-ESRGAN_API_文档.md @@ -0,0 +1,902 @@ +# Real-ESRGAN API 接口文档 + +## 服务信息 + +- **服务地址**: http://156.225.27.52:5233(通过frpc穿透) +- **本地端口**: 8002(仅服务器内部访问) +- **远程端口**: 5233(外部访问端口) +- **API版本**: 1.0.0 +- **模型**: Real-ESRGAN_x4plus(图像超分辨率模型) +- **框架**: FastAPI +- **API文档**: http://156.225.27.52:5233/docs +- **OpenAPI规范**: http://156.225.27.52:5233/openapi.json +- **API密钥**: 所有核心接口需要API密钥认证(详见下方认证说明) + +--- + +## 认证方式 + +所有核心接口(`/upscale/file`、`/upscale/base64` 和 `/upscale/url`)需要在请求头中添加API密钥: + +**请求头**: +``` +X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**注意**: 以下端点无需API密钥: +- `GET /` - 根端点(服务状态检查) +- `GET /health` - 健康检查 +- `GET /api-key-info` - API密钥信息 +- `GET /models` - 支持的模型列表 + +--- + +## 接口列表 + +### 1. 根端点 - 服务状态检查 + +**接口地址**: `GET /` + +**描述**: 检查服务是否正常运行 + +**请求示例**: +```bash +curl http://156.225.27.52:5233/ +``` + +**响应示例**: +```json +{ + "message": "Real-ESRGAN API 服务运行正常", + "version": "1.0.0", + "model": "RealESRGAN_x4plus", + "scale": 4 +} +``` + +--- + +### 2. 健康检查 + +**接口地址**: `GET /health` + +**描述**: 检查服务健康状态和模型加载状态(无需API密钥) + +**请求示例**: +```bash +curl http://156.225.27.52:5233/health +``` + +**响应示例**: +```json +{ + "status": "healthy", + "model_loaded": true, + "model": "RealESRGAN_x4plus", + "scale": 4, + "device": "cuda" +} +``` + +--- + +### 3. API密钥信息 + +**接口地址**: `GET /api-key-info` + +**描述**: 获取API密钥使用说明(无需API密钥) + +**请求示例**: +```bash +curl http://156.225.27.52:5233/api-key-info +``` + +**响应示例**: +```json +{ + "api_key_name": "X-API-Key", + "description": "请在请求头中添加API密钥" +} +``` + +--- + +### 4. 支持的模型列表 + +**接口地址**: `GET /models` + +**描述**: 获取所有支持的模型列表(无需API密钥) + +**请求示例**: +```bash +curl http://156.225.27.52:5233/models +``` + +**响应示例**: +```json +{ + "models": [ + { + "name": "RealESRGAN_x2plus", + "scale": 2, + "description": "2倍放大模型" + }, + { + "name": "RealESRGAN_x4plus", + "scale": 4, + "description": "4倍放大模型(推荐)" + }, + { + "name": "RealESRGAN_x8plus", + "scale": 8, + "description": "8倍放大模型" + }, + { + "name": "RealESRGAN_animevideov3", + "scale": 4, + "description": "动漫视频模型" + } + ] +} +``` + +--- + +### 5. 文件上传方式放大图像 + +**接口地址**: `POST /upscale/file` + +**描述**: 通过文件上传方式对图片进行超分辨率处理(需要API密钥) + +**Content-Type**: `multipart/form-data` + +**请求头**: +``` +X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**请求参数**: +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| file | File | 是 | - | 图片文件(支持常见图片格式) | +| scale | int | 否 | 4 | 放大倍数(2, 4, 8) | +| model_name | string | 否 | RealESRGAN_x4plus | 模型名称 | +| tile | int | 否 | 0 | 分块大小(0=不使用分块,可根据显存调整) | +| half | bool | 否 | false | 是否使用FP16(降低显存需求) | + +**请求示例 (cURL)**: +```bash +curl -X POST http://156.225.27.52:5233/upscale/file \ + -H "X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw" \ + -F "file=@/path/to/your/image.jpg" \ + -F "scale=4" \ + -F "model_name=RealESRGAN_x4plus" +``` + +**请求示例 (Python)**: +```python +import requests + +url = "http://156.225.27.52:5233/upscale/file" +headers = { + "X-API-Key": "realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw" +} +files = {"file": open("image.jpg", "rb")} +params = { + "scale": 4, + "model_name": "RealESRGAN_x4plus", + "tile": 0, + "half": False +} +response = requests.post(url, files=files, headers=headers, params=params) + +# 保存结果 +with open("result.png", "wb") as f: + f.write(response.content) + +print(f"处理时间: {response.headers.get('X-Processing-Time')} 秒") +print(f"放大倍数: {response.headers.get('X-Scale')}x") +print(f"使用模型: {response.headers.get('X-Model')}") +``` + +**请求示例 (JavaScript/Node.js)**: +```javascript +const FormData = require('form-data'); +const fs = require('fs'); +const axios = require('axios'); + +const form = new FormData(); +form.append('file', fs.createReadStream('image.jpg')); +form.append('scale', '4'); +form.append('model_name', 'RealESRGAN_x4plus'); + +axios.post('http://156.225.27.52:5233/upscale/file', form, { + headers: { + ...form.getHeaders(), + 'X-API-Key': 'realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw' + }, + responseType: 'arraybuffer' +}) +.then(response => { + fs.writeFileSync('result.png', response.data); + console.log('处理时间:', response.headers['x-processing-time'], '秒'); + console.log('放大倍数:', response.headers['x-scale'], 'x'); +}); +``` + +**响应**: +- **Content-Type**: `image/png` +- **Body**: PNG格式的图片二进制数据(超分辨率处理后的图片) +- **Headers**: + - `X-Processing-Time`: 处理时间(秒) + - `X-Scale`: 放大倍数 + - `X-Model`: 使用的模型名称 + +**错误响应**: +```json +{ + "detail": "Invalid API key" +} +``` +(当API密钥无效或缺失时) + +--- + +### 6. Base64方式放大图像 + +**接口地址**: `POST /upscale/base64` + +**描述**: 通过Base64编码方式对图片进行超分辨率处理(需要API密钥) + +**Content-Type**: `application/json` + +**请求头**: +``` +X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**请求体**: +```json +{ + "image_base64": "base64编码的图片字符串", + "scale": 4, + "model_name": "RealESRGAN_x4plus" +} +``` + +**请求参数**: +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| image_base64 | string | 是 | - | Base64编码的图片数据 | +| scale | int | 否 | 4 | 放大倍数(2, 4, 8) | +| model_name | string | 否 | RealESRGAN_x4plus | 模型名称 | +| tile | int | 否 | 0 | 分块大小(Query参数) | +| half | bool | 否 | false | 是否使用FP16(Query参数) | + +**请求示例 (cURL)**: +```bash +curl -X POST http://156.225.27.52:5233/upscale/base64 \ + -H "X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw" \ + -H "Content-Type: application/json" \ + -d '{ + "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", + "scale": 4, + "model_name": "RealESRGAN_x4plus" + }' +``` + +**请求示例 (Python)**: +```python +import requests +import base64 + +# 读取图片并转换为base64 +with open("image.jpg", "rb") as f: + image_base64 = base64.b64encode(f.read()).decode("utf-8") + +url = "http://156.225.27.52:5233/upscale/base64" +headers = { + "X-API-Key": "realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw", + "Content-Type": "application/json" +} +data = { + "image_base64": image_base64, + "scale": 4, + "model_name": "RealESRGAN_x4plus" +} +params = { + "tile": 0, + "half": False +} +response = requests.post(url, json=data, headers=headers, params=params) + +if response.status_code == 200: + result = response.json() + # 保存结果 + result_image = base64.b64decode(result["image_base64"]) + with open("result.png", "wb") as f: + f.write(result_image) + print(f"✅ 成功! 处理时间: {result['processing_time']:.2f}秒") +``` + +**请求示例 (JavaScript/Node.js)**: +```javascript +const fs = require('fs'); +const axios = require('axios'); + +// 读取图片并转换为base64 +const imageBuffer = fs.readFileSync('image.jpg'); +const imageBase64 = imageBuffer.toString('base64'); + +axios.post('http://156.225.27.52:5233/upscale/base64', { + image_base64: imageBase64, + scale: 4, + model_name: 'RealESRGAN_x4plus' +}, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': 'realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw' + }, + params: { + tile: 0, + half: false + } +}) +.then(response => { + if (response.data.success) { + const resultImage = Buffer.from(response.data.image_base64, 'base64'); + fs.writeFileSync('result.png', resultImage); + console.log('处理时间:', response.data.processing_time, '秒'); + } +}); +``` + +**响应示例**: +```json +{ + "success": true, + "image_base64": "iVBORw0KGgoAAAANSUhEUgAA...", + "processing_time": 2.345, + "scale": 4, + "model": "RealESRGAN_x4plus" +} +``` + +**响应参数**: +| 参数名 | 类型 | 说明 | +|--------|------|------| +| success | boolean | 是否成功 | +| image_base64 | string | Base64编码的结果图片(PNG格式) | +| processing_time | number | 处理时间(秒) | +| scale | int | 放大倍数 | +| model | string | 使用的模型名称 | + +--- + +### 7. URL方式放大图像(上传到S3) + +**接口地址**: `POST /upscale/url` + +**描述**: 通过图片URL对图片进行超分辨率处理,处理后的图片自动上传到S3存储桶并返回URL(需要API密钥) + +**Content-Type**: `application/json` + +**请求头**: +``` +X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**请求体**: +```json +{ + "image_url": "https://example.com/image.jpg", + "scale": 4, + "model_name": "RealESRGAN_x4plus" +} +``` + +**请求参数**: +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +|--------|------|------|--------|------| +| image_url | string | 是 | - | 图片的URL地址 | +| scale | int | 否 | 4 | 放大倍数(2, 4, 8) | +| model_name | string | 否 | RealESRGAN_x4plus | 模型名称 | +| tile | int | 否 | 0 | 分块大小(Query参数) | +| half | bool | 否 | false | 是否使用FP16(Query参数) | + +**请求示例 (cURL)**: +```bash +curl -X POST http://156.225.27.52:5233/upscale/url \ + -H "X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw" \ + -H "Content-Type: application/json" \ + -d '{ + "image_url": "https://example.com/image.jpg", + "scale": 4, + "model_name": "RealESRGAN_x4plus" + }' +``` + +**请求示例 (Python)**: +```python +import requests + +url = "http://156.225.27.52:5233/upscale/url" +headers = { + "X-API-Key": "realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw", + "Content-Type": "application/json" +} +data = { + "image_url": "https://example.com/image.jpg", + "scale": 4, + "model_name": "RealESRGAN_x4plus" +} +params = { + "tile": 0, + "half": False +} +response = requests.post(url, json=data, headers=headers, params=params) + +if response.status_code == 200: + result = response.json() + print(f"✅ 成功!") + print(f"处理后的图片URL: {result['image_url']}") + print(f"下载时间: {result['download_time']:.2f}秒") + print(f"处理时间: {result['processing_time']:.2f}秒") + print(f"上传时间: {result['upload_time']:.2f}秒") + print(f"总时间: {result['total_time']:.2f}秒") +``` + +**请求示例 (JavaScript/Node.js)**: +```javascript +const axios = require('axios'); + +axios.post('http://156.225.27.52:5233/upscale/url', { + image_url: 'https://example.com/image.jpg', + scale: 4, + model_name: 'RealESRGAN_x4plus' +}, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': 'realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw' + }, + params: { + tile: 0, + half: false + } +}) +.then(response => { + if (response.data.success) { + console.log('✅ 成功!'); + console.log('处理后的图片URL:', response.data.image_url); + console.log('下载时间:', response.data.download_time, '秒'); + console.log('处理时间:', response.data.processing_time, '秒'); + console.log('上传时间:', response.data.upload_time, '秒'); + console.log('总时间:', response.data.total_time, '秒'); + } +}) +.catch(error => { + console.error('❌ 失败:', error.response?.data || error.message); +}); +``` + +**响应示例**: +```json +{ + "success": true, + "image_url": "https://s3.1080p.hk/jsxiangche/realesrgan/20260104/abc123def456.png", + "processing_time": 2.345, + "download_time": 0.856, + "upload_time": 0.234, + "total_time": 3.435, + "scale": 4, + "model": "RealESRGAN_x4plus" +} +``` + +**响应参数**: +| 参数名 | 类型 | 说明 | +|--------|------|------| +| success | boolean | 是否成功 | +| image_url | string | 处理后的图片在S3中的URL地址 | +| processing_time | number | 图片处理时间(秒) | +| download_time | number | 图片下载时间(秒) | +| upload_time | number | 图片上传到S3的时间(秒) | +| total_time | number | 总耗时(秒) | +| scale | int | 放大倍数 | +| model | string | 使用的模型名称 | + +**注意事项**: +- 此接口需要S3存储功能已启用(`USE_S3_STORAGE=true`) +- 图片URL必须可公开访问 +- 支持的图片格式:JPG, PNG, JPEG等常见格式 +- 图片大小限制:最大50MB +- 处理后的图片会自动上传到S3存储桶并返回可访问的URL + +--- + +## 服务管理 + +Real-ESRGAN API服务通过systemd管理,支持自动启动和崩溃自动重启。 + +### 服务状态检查 + +```bash +# 查看服务状态 +systemctl status realesrgan-api.service + +# 查看服务日志 +journalctl -u realesrgan-api.service -f + +# 查看最近100行日志 +journalctl -u realesrgan-api.service -n 100 +``` + +### 服务控制命令 + +```bash +# 启动服务 +systemctl start realesrgan-api.service + +# 停止服务 +systemctl stop realesrgan-api.service + +# 重启服务 +systemctl restart realesrgan-api.service + +# 重新加载配置(无需重启) +systemctl reload realesrgan-api.service + +# 查看服务状态 +systemctl status realesrgan-api.service +``` + +### 自动启动配置 + +服务已配置为系统启动时自动启动,崩溃后自动重启: + +- **自动启动**: 服务已启用(`systemctl enable realesrgan-api.service`) +- **崩溃重启**: `Restart=always`,服务崩溃后自动重启 +- **重启延迟**: `RestartSec=10`,崩溃后等待10秒再重启 +- **重启策略**: 服务异常退出时会自动重启,无需手动干预 + +### 服务配置 + +服务配置文件位置:`/etc/systemd/system/realesrgan-api.service` + +主要配置项: +- **工作目录**: `/root/realesrgan-api` +- **运行用户**: `root` +- **GPU设备**: `CUDA_VISIBLE_DEVICES=1`(使用GPU1) +- **S3存储**: 已启用,环境变量已配置 +- **日志输出**: 通过systemd journal管理 + +### 日志查看 + +```bash +# 实时查看日志 +journalctl -u realesrgan-api.service -f + +# 查看今天的日志 +journalctl -u realesrgan-api.service --since today + +# 查看最近的错误日志 +journalctl -u realesrgan-api.service -p err -n 50 +``` + +--- + +## 错误码说明 + +| HTTP状态码 | 说明 | +|-----------|------| +| 200 | 请求成功 | +| 400 | 请求参数错误(如文件格式不支持、图片URL无效等) | +| 401 | 未授权(API密钥无效或缺失) | +| 422 | 请求参数验证失败 | +| 500 | 服务器内部错误(如处理失败、S3上传失败等) | + +--- + +## 注意事项 + +1. **服务地址**: + - 外部访问地址:http://156.225.27.52:5233(通过frpc穿透) + - 本地端口:8002(仅服务器内部访问) + - 远程端口:5233(外部访问端口) + +2. **API密钥认证**: + - 所有核心接口(`/upscale/file`、`/upscale/base64` 和 `/upscale/url`)都需要在请求头中添加 `X-API-Key` + - API密钥:`realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw` + - 健康检查、模型列表、API密钥信息等端点无需密钥 + +3. **支持的图片格式**: 所有常见图片格式(JPG, PNG, JPEG等) + +4. **输出格式**: 所有接口返回PNG格式的图片 + +5. **文件大小限制**: + - 上传文件:建议不超过50MB + - URL方式:图片下载大小限制50MB + +6. **处理时间**: 处理时间取决于图片大小和服务器负载,通常在1-10秒之间 + +7. **放大倍数**: 支持2x、4x、8x放大,根据模型而定 + +8. **模型选择**: + - `RealESRGAN_x4plus`:推荐,4倍放大,适合大多数场景 + - `RealESRGAN_x2plus`:2倍放大,处理速度快 + - `RealESRGAN_x8plus`:8倍放大,需要更多显存 + - `RealESRGAN_animevideov3`:动漫视频模型,4倍放大 + +9. **S3存储功能**: + - `/upscale/url` 接口需要S3存储功能启用 + - S3配置可通过环境变量设置 + - 处理后的图片会自动上传到S3并返回可访问的URL + +10. **CORS**: 服务已配置CORS,支持跨域请求 + +11. **服务管理**: + - 服务通过systemd管理,服务名称:`realesrgan-api.service` + - 系统重启后服务会自动启动(已启用) + - 服务崩溃后会在10秒内自动重启(`Restart=always`) + - 日志通过systemd journal管理,使用 `journalctl -u realesrgan-api.service` 查看 + - 服务控制命令:`systemctl start/stop/restart/status realesrgan-api.service` + +--- + +## 完整使用示例 + +### Python完整示例 + +```python +import requests +import base64 +from pathlib import Path + +API_KEY = "realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw" +BASE_URL = "http://156.225.27.52:5233" + +# 方法1: 文件上传 +def upscale_file(image_path): + url = f"{BASE_URL}/upscale/file" + headers = {"X-API-Key": API_KEY} + + with open(image_path, "rb") as f: + files = {"file": f} + params = {"scale": 4, "model_name": "RealESRGAN_x4plus"} + response = requests.post(url, files=files, headers=headers, params=params) + + if response.status_code == 200: + output_path = Path(image_path).stem + "_upscaled.png" + with open(output_path, "wb") as f: + f.write(response.content) + print(f"✅ 成功! 结果已保存到: {output_path}") + return output_path + else: + print(f"❌ 失败: {response.json()}") + return None + +# 方法2: Base64 +def upscale_base64(image_path): + url = f"{BASE_URL}/upscale/base64" + headers = { + "X-API-Key": API_KEY, + "Content-Type": "application/json" + } + + with open(image_path, "rb") as f: + image_base64 = base64.b64encode(f.read()).decode("utf-8") + + data = { + "image_base64": image_base64, + "scale": 4, + "model_name": "RealESRGAN_x4plus" + } + response = requests.post(url, json=data, headers=headers) + + if response.status_code == 200: + result = response.json() + result_image = base64.b64decode(result["image_base64"]) + output_path = Path(image_path).stem + "_upscaled.png" + with open(output_path, "wb") as f: + f.write(result_image) + print(f"✅ 成功! 处理时间: {result['processing_time']:.2f}秒") + return output_path + else: + print(f"❌ 失败: {response.json()}") + return None + +# 方法3: URL方式(上传到S3) +def upscale_url(image_url): + url = f"{BASE_URL}/upscale/url" + headers = { + "X-API-Key": API_KEY, + "Content-Type": "application/json" + } + + data = { + "image_url": image_url, + "scale": 4, + "model_name": "RealESRGAN_x4plus" + } + response = requests.post(url, json=data, headers=headers) + + if response.status_code == 200: + result = response.json() + print(f"✅ 成功!") + print(f"处理后的图片URL: {result['image_url']}") + print(f"总时间: {result['total_time']:.2f}秒") + return result["image_url"] + else: + print(f"❌ 失败: {response.json()}") + return None + +# 使用示例 +if __name__ == "__main__": + image_path = "test_image.jpg" + + # 使用文件上传方式 + upscale_file(image_path) + + # 或使用Base64方式 + # upscale_base64(image_path) + + # 或使用URL方式 + # upscale_url("https://example.com/image.jpg") +``` + +### JavaScript/Node.js完整示例 + +```javascript +const FormData = require('form-data'); +const fs = require('fs'); +const axios = require('axios'); +const path = require('path'); + +const API_KEY = 'realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw'; +const BASE_URL = 'http://156.225.27.52:5233'; + +// 方法1: 文件上传 +async function upscaleFile(imagePath) { + const form = new FormData(); + form.append('file', fs.createReadStream(imagePath)); + form.append('scale', '4'); + form.append('model_name', 'RealESRGAN_x4plus'); + + try { + const response = await axios.post(`${BASE_URL}/upscale/file`, form, { + headers: { + ...form.getHeaders(), + 'X-API-Key': API_KEY + }, + responseType: 'arraybuffer' + }); + + const outputPath = path.parse(imagePath).name + '_upscaled.png'; + fs.writeFileSync(outputPath, response.data); + console.log(`✅ 成功! 结果已保存到: ${outputPath}`); + return outputPath; + } catch (error) { + console.error('❌ 失败:', error.response?.data || error.message); + return null; + } +} + +// 方法2: Base64 +async function upscaleBase64(imagePath) { + const imageBuffer = fs.readFileSync(imagePath); + const imageBase64 = imageBuffer.toString('base64'); + + try { + const response = await axios.post(`${BASE_URL}/upscale/base64`, { + image_base64: imageBase64, + scale: 4, + model_name: 'RealESRGAN_x4plus' + }, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': API_KEY + } + }); + + if (response.data.success) { + const outputPath = path.parse(imagePath).name + '_upscaled.png'; + const resultImage = Buffer.from(response.data.image_base64, 'base64'); + fs.writeFileSync(outputPath, resultImage); + console.log(`✅ 成功! 处理时间: ${response.data.processing_time.toFixed(2)}秒`); + return outputPath; + } + } catch (error) { + console.error('❌ 失败:', error.response?.data || error.message); + return null; + } +} + +// 方法3: URL方式(上传到S3) +async function upscaleUrl(imageUrl) { + try { + const response = await axios.post(`${BASE_URL}/upscale/url`, { + image_url: imageUrl, + scale: 4, + model_name: 'RealESRGAN_x4plus' + }, { + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': API_KEY + } + }); + + if (response.data.success) { + console.log('✅ 成功!'); + console.log('处理后的图片URL:', response.data.image_url); + console.log('总时间:', response.data.total_time.toFixed(2), '秒'); + return response.data.image_url; + } + } catch (error) { + console.error('❌ 失败:', error.response?.data || error.message); + return null; + } +} + +// 使用示例 +(async () => { + const imagePath = 'test_image.jpg'; + + // 使用文件上传方式 + await upscaleFile(imagePath); + + // 或使用Base64方式 + // await upscaleBase64(imagePath); + + // 或使用URL方式 + // await upscaleUrl('https://example.com/image.jpg'); +})(); +``` + +--- + +## 交互式API文档 + +访问 http://156.225.27.52:5233/docs 可以在浏览器中查看交互式API文档(Swagger UI),支持在线测试接口。 + +**注意**: 在Swagger UI中测试需要API密钥的接口时,需要先点击右上角的"Authorize"按钮(如果有),或者直接在请求头中添加 `X-API-Key`。 + +--- + +## API密钥 + +当前API密钥: +``` +realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**请求头格式**: +``` +X-API-Key: realesrgan_sk_live_MAPF4kLW5QinFz5BO96w-oVk09WnmvBZSyNDG8AxUgo7HGlqssJ4y1XZ38SyQsG9QCsYYOLcp9df6Ku1Jw78Pw +``` + +**安全提示**: +- 请妥善保管API密钥,不要将其暴露在客户端代码中 +- 建议通过环境变量或配置文件管理API密钥 + +--- + +## S3存储配置 + +如果使用 `/upscale/url` 接口,需要配置S3存储。配置方式如下: + +**环境变量**: +```bash +USE_S3_STORAGE=true +MINIO_S3_ENDPOINT=s3.1080p.hk +MINIO_S3_USE_SSL=true +MINIO_S3_BUCKET=jsxiangche +MINIO_S3_ACCESS_KEY=AEvT1p0wSNjow3Asu9NV +MINIO_S3_SECRET_KEY=u8nzFl1S6dNoT5PQaQhkHz8Du6iX0hVnPswtvHhs +MINIO_S3_REGION=us-east-1 +``` + +**说明**: +- `USE_S3_STORAGE`: 必须设置为 `true` 才能启用S3上传功能 +- 处理后的图片会保存到S3存储桶中,路径格式:`realesrgan/YYYYMMDD/UUID.png` +- 返回的URL可以直接访问和下载 + diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 7321251..e65f394 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -355,6 +355,12 @@ export function AdminSettingsClient() { apiKey: "", type: "clean", // "clean" | "face" scaleFactor: "auto", // "auto" | "1" | "2" | "4" + // 备用API配置 (Real-ESRGAN) + useBackupApi: false, // 是否启用备用API + backupApiUrl: "http://156.225.27.52:5233", // 备用API地址 + backupApiKey: "", // 备用API密钥 + backupScale: "4", // 备用API放大倍数 "2" | "4" | "8" + backupModel: "RealESRGAN_x4plus", // 备用API模型 }); // 图像拓展API配置状态 @@ -385,12 +391,22 @@ export function AdminSettingsClient() { apiKey?: string; type?: string; scaleFactor?: number; + useBackupApi?: boolean; + backupApiUrl?: string; + backupApiKey?: string; + backupScale?: string; + backupModel?: string; }; setEnhanceConfig({ apiUrl: config.apiUrl || "", apiKey: config.apiKey || "", type: config.type || "clean", scaleFactor: config.scaleFactor ? String(config.scaleFactor) : "auto", + useBackupApi: config.useBackupApi || false, + backupApiUrl: config.backupApiUrl || "http://156.225.27.52:5233", + backupApiKey: config.backupApiKey || "", + backupScale: config.backupScale || "4", + backupModel: config.backupModel || "RealESRGAN_x4plus", }); } }, [enhanceConfigData]); @@ -722,14 +738,26 @@ export function AdminSettingsClient() { // --- 变清晰API配置逻辑 --- const handleSaveEnhanceConfig = async () => { try { - if (!enhanceConfig.apiUrl.trim()) { - toast.error("请输入API地址"); - return; - } - - if (!enhanceConfig.apiKey.trim()) { - toast.error("请输入API密钥"); - return; + // 如果启用了备用API,验证备用API配置 + if (enhanceConfig.useBackupApi) { + if (!enhanceConfig.backupApiUrl.trim()) { + toast.error("请输入备用API地址"); + return; + } + if (!enhanceConfig.backupApiKey.trim()) { + toast.error("请输入备用API密钥"); + return; + } + } else { + // 如果使用主API,验证主API配置 + if (!enhanceConfig.apiUrl.trim()) { + toast.error("请输入API地址"); + return; + } + if (!enhanceConfig.apiKey.trim()) { + toast.error("请输入API密钥"); + return; + } } const response = await fetch("/api/admin/settings", { @@ -742,6 +770,12 @@ export function AdminSettingsClient() { apiKey: enhanceConfig.apiKey.trim(), type: enhanceConfig.type, scaleFactor: enhanceConfig.scaleFactor && enhanceConfig.scaleFactor !== "auto" ? Number.parseInt(enhanceConfig.scaleFactor) : undefined, + // 备用API配置 + useBackupApi: enhanceConfig.useBackupApi, + backupApiUrl: enhanceConfig.backupApiUrl.trim(), + backupApiKey: enhanceConfig.backupApiKey.trim(), + backupScale: enhanceConfig.backupScale, + backupModel: enhanceConfig.backupModel, }, }), }); @@ -3085,83 +3119,190 @@ export function AdminSettingsClient() { 变清晰API配置 - 配置图片变清晰API,用于画图标注功能中的图片增强处理 + 配置图片变清晰API,用于画图标注功能中的图片增强处理。支持主API和备用API切换。 -
- - setEnhanceConfig({ ...enhanceConfig, apiUrl: e.target.value })} - placeholder="https://techsz.aoscdn.com" + {/* API切换开关 */} +
+
+ +
+ 启用后将使用Real-ESRGAN本地模型进行图片增强,效果更好 +
+
+ setEnhanceConfig({ ...enhanceConfig, useBackupApi: checked })} /> -
- 请输入完整的API地址,例如: https://techsz.aoscdn.com -
-
-
- - setEnhanceConfig({ ...enhanceConfig, apiKey: e.target.value })} - placeholder="请输入API密钥" - /> -
- API密钥是必需的,请从服务提供商获取 -
-
-
- - -
- 选择图片类型:通用或人像(人像模式最多同时处理10个人) -
-
-
- - -
- 选择放大倍数:自动(根据图片尺寸智能选择)、不放大、2倍或4倍 -
-
-
-
API文档说明
-
-

• API地址应指向服务根地址,例如: https://techsz.aoscdn.com

-

• 系统会自动调用 /api/tasks/visual/scale 接口进行变清晰处理

-

• 支持的图片格式:JPG, JPEG, PNG, BMP, WEBP, TIFF等

-

• 图片大小:不超过20MB,分辨率最大4096x4096

-

• 返回的链接有效期为1小时,系统会自动下载并存储到S3

-
+ + {/* 主API配置 */} + {!enhanceConfig.useBackupApi && ( + <> + +
主API配置 (第三方云服务)
+
+ + setEnhanceConfig({ ...enhanceConfig, apiUrl: e.target.value })} + placeholder="https://techsz.aoscdn.com" + /> +
+ 请输入完整的API地址,例如: https://techsz.aoscdn.com +
+
+
+ + setEnhanceConfig({ ...enhanceConfig, apiKey: e.target.value })} + placeholder="请输入API密钥" + /> +
+ API密钥是必需的,请从服务提供商获取 +
+
+
+ + +
+ 选择图片类型:通用或人像(人像模式最多同时处理10个人) +
+
+
+ + +
+ 选择放大倍数:自动(根据图片尺寸智能选择)、不放大、2倍或4倍 +
+
+
+
主API文档说明
+
+

• API地址应指向服务根地址,例如: https://techsz.aoscdn.com

+

• 系统会自动调用 /api/tasks/visual/scale 接口进行变清晰处理

+

• 支持的图片格式:JPG, JPEG, PNG, BMP, WEBP, TIFF等

+

• 图片大小:不超过20MB,分辨率最大4096x4096

+

• 返回的链接有效期为1小时,系统会自动下载并存储到S3

+
+
+ + )} + + {/* 备用API配置 (Real-ESRGAN) */} + {enhanceConfig.useBackupApi && ( + <> + +
备用API配置 (Real-ESRGAN 本地模型)
+
+ + setEnhanceConfig({ ...enhanceConfig, backupApiUrl: e.target.value })} + placeholder="http://156.225.27.52:5233" + /> +
+ Real-ESRGAN API服务地址,默认: http://156.225.27.52:5233 +
+
+
+ + setEnhanceConfig({ ...enhanceConfig, backupApiKey: e.target.value })} + placeholder="请输入Real-ESRGAN API密钥" + /> +
+ API密钥用于认证,请从管理员处获取 +
+
+
+
+ + +
+ 推荐使用4倍放大,效果最佳 +
+
+
+ + +
+ 不同模型适用于不同场景 +
+
+
+
+
Real-ESRGAN API说明
+
+

• 基于Real-ESRGAN深度学习模型,图像超分辨率效果更好

+

• 使用URL方式上传图片,处理后自动保存到S3存储

+

• 支持2x、4x、8x放大倍数

+

• 处理时间:通常1-10秒,取决于图片大小

+

• 支持格式:JPG, PNG, JPEG等常见图片格式

+

• 文件限制:最大50MB

+
+
+ + )} + diff --git a/app/api/enhance/route.ts b/app/api/enhance/route.ts index cda828a..1492b5d 100644 --- a/app/api/enhance/route.ts +++ b/app/api/enhance/route.ts @@ -5,6 +5,20 @@ import { uploadFile } from "@/lib/storage/service"; import { db } from "@/lib/db"; import { generatedImage } from "@/lib/db/schema"; +// 变清晰API配置类型 +type EnhanceApiConfig = { + apiUrl?: string; + apiKey?: string; + type?: string; // "clean" | "face" + scaleFactor?: number | string; // 1 | 2 | 4 | "auto" + // 备用API配置 (Real-ESRGAN) + useBackupApi?: boolean; + backupApiUrl?: string; + backupApiKey?: string; + backupScale?: string; // "2" | "4" | "8" + backupModel?: string; +}; + /** * 压缩图片生成缩略图(使用 sharp) */ @@ -84,6 +98,150 @@ async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/jp // 设置API路由最大执行时间为3分钟(180秒) export const maxDuration = 180; +/** + * 使用Real-ESRGAN备用API处理图片(通过URL方式) + * @param imageUrl 图片URL(需要公开可访问) + * @param config 备用API配置 + * @returns 处理后的图片Base64和S3 URL + */ +async function processWithRealESRGAN( + imageUrl: string, + config: { + backupApiUrl: string; + backupApiKey: string; + backupScale: string; + backupModel: string; + } +): Promise<{ imageBase64: string; imageUrl: string; processingTime: number }> { + const startTime = Date.now(); + const apiUrl = config.backupApiUrl.endsWith("/") + ? config.backupApiUrl.slice(0, -1) + : config.backupApiUrl; + + const upscaleUrl = `${apiUrl}/upscale/url`; + console.log(`[Enhance-RealESRGAN] 调用URL方式API: ${upscaleUrl}`); + console.log(`[Enhance-RealESRGAN] 图片URL: ${imageUrl}`); + console.log(`[Enhance-RealESRGAN] 放大倍数: ${config.backupScale}x, 模型: ${config.backupModel}`); + + const response = await fetch(upscaleUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-Key": config.backupApiKey, + }, + body: JSON.stringify({ + image_url: imageUrl, + scale: Number.parseInt(config.backupScale) || 4, + model_name: config.backupModel || "RealESRGAN_x4plus", + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[Enhance-RealESRGAN] API请求失败: ${response.status} ${response.statusText}`); + console.error(`[Enhance-RealESRGAN] 错误详情: ${errorText}`); + throw new Error(`Real-ESRGAN API请求失败: ${response.status} - ${errorText || response.statusText}`); + } + + const result = await response.json(); + console.log(`[Enhance-RealESRGAN] API响应:`, { + success: result.success, + hasImageUrl: !!result.image_url, + processingTime: result.processing_time, + totalTime: result.total_time, + }); + + if (!result.success || !result.image_url) { + throw new Error(`Real-ESRGAN处理失败: ${JSON.stringify(result)}`); + } + + // Real-ESRGAN API直接返回S3 URL,我们需要下载图片并转换为Base64 + console.log(`[Enhance-RealESRGAN] 下载处理后的图片: ${result.image_url}`); + const imageResponse = await fetch(result.image_url); + if (!imageResponse.ok) { + throw new Error(`无法下载Real-ESRGAN处理后的图片: ${imageResponse.status}`); + } + + const imageBuffer = await imageResponse.arrayBuffer(); + const imageBase64 = Buffer.from(imageBuffer).toString("base64"); + + const processingTime = (Date.now() - startTime) / 1000; + console.log(`[Enhance-RealESRGAN] ✅ 处理完成,总耗时: ${processingTime.toFixed(2)}秒`); + console.log(`[Enhance-RealESRGAN] ✅ 处理后图片大小: ${imageBuffer.byteLength} bytes`); + + return { + imageBase64, + imageUrl: result.image_url, + processingTime, + }; +} + +/** + * 使用Real-ESRGAN备用API处理图片(通过Base64方式,用于没有公开URL的图片) + * @param imageBase64 图片Base64数据 + * @param config 备用API配置 + * @returns 处理后的图片Base64 + */ +async function processWithRealESRGANBase64( + imageBase64: string, + config: { + backupApiUrl: string; + backupApiKey: string; + backupScale: string; + backupModel: string; + } +): Promise<{ imageBase64: string; processingTime: number }> { + const startTime = Date.now(); + const apiUrl = config.backupApiUrl.endsWith("/") + ? config.backupApiUrl.slice(0, -1) + : config.backupApiUrl; + + const upscaleUrl = `${apiUrl}/upscale/base64`; + console.log(`[Enhance-RealESRGAN] 调用Base64方式API: ${upscaleUrl}`); + console.log(`[Enhance-RealESRGAN] 放大倍数: ${config.backupScale}x, 模型: ${config.backupModel}`); + console.log(`[Enhance-RealESRGAN] 输入图片Base64长度: ${imageBase64.length}`); + + const response = await fetch(upscaleUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-API-Key": config.backupApiKey, + }, + body: JSON.stringify({ + image_base64: imageBase64, + scale: Number.parseInt(config.backupScale) || 4, + model_name: config.backupModel || "RealESRGAN_x4plus", + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`[Enhance-RealESRGAN] API请求失败: ${response.status} ${response.statusText}`); + console.error(`[Enhance-RealESRGAN] 错误详情: ${errorText}`); + throw new Error(`Real-ESRGAN API请求失败: ${response.status} - ${errorText || response.statusText}`); + } + + const result = await response.json(); + console.log(`[Enhance-RealESRGAN] API响应:`, { + success: result.success, + hasImageBase64: !!result.image_base64, + processingTime: result.processing_time, + }); + + if (!result.success || !result.image_base64) { + throw new Error(`Real-ESRGAN处理失败: ${JSON.stringify(result)}`); + } + + const processingTime = (Date.now() - startTime) / 1000; + console.log(`[Enhance-RealESRGAN] ✅ 处理完成,总耗时: ${processingTime.toFixed(2)}秒`); + console.log(`[Enhance-RealESRGAN] ✅ 处理后图片Base64长度: ${result.image_base64.length}`); + + return { + imageBase64: result.image_base64, + processingTime, + }; +} + /** * 轮询获取任务结果(根据官方示例) */ @@ -184,18 +342,28 @@ export async function POST(request: NextRequest) { ); } - const config = enhanceConfig.value as { - apiUrl?: string; - apiKey?: string; - type?: string; // "clean" | "face" - scaleFactor?: number | string; // 1 | 2 | 4 | "auto" - }; + const config = enhanceConfig.value as EnhanceApiConfig; - if (!config.apiUrl || !config.apiKey) { - return NextResponse.json( - { error: "变清晰API地址或密钥未配置" }, - { status: 400 } - ); + // 检查是否使用备用API + const useBackupApi = config.useBackupApi === true; + console.log(`[Enhance API] 使用${useBackupApi ? "备用API (Real-ESRGAN)" : "主API"}`); + + if (useBackupApi) { + // 验证备用API配置 + if (!config.backupApiUrl || !config.backupApiKey) { + return NextResponse.json( + { error: "备用变清晰API地址或密钥未配置" }, + { status: 400 } + ); + } + } else { + // 验证主API配置 + if (!config.apiUrl || !config.apiKey) { + return NextResponse.json( + { error: "变清晰API地址或密钥未配置" }, + { status: 400 } + ); + } } // 2. 获取请求体(Base64图片或FormData) @@ -250,11 +418,86 @@ export async function POST(request: NextRequest) { ); } + // ========== 备用API处理路径 (Real-ESRGAN) ========== + if (useBackupApi) { + console.log("[Enhance API] 使用Real-ESRGAN备用API处理..."); + + try { + // 使用Base64方式调用Real-ESRGAN API + const result = await processWithRealESRGANBase64(imageBase64, { + backupApiUrl: config.backupApiUrl!, + backupApiKey: config.backupApiKey!, + backupScale: config.backupScale || "4", + backupModel: config.backupModel || "RealESRGAN_x4plus", + }); + + // 上传处理后的图片到S3 + let thumbnailUrl: string; + let originalUrl: string; + + try { + console.log(`[Enhance-RealESRGAN] 📤 上传处理后的图片到S3...`); + const uploadResult = await uploadBase64ToS3(result.imageBase64, "image/png"); + thumbnailUrl = uploadResult.thumbnail; + originalUrl = uploadResult.original; + console.log(`[Enhance-RealESRGAN] ✅ 上传成功: ${originalUrl}`); + } catch (uploadError) { + console.error("[Enhance-RealESRGAN] ❌ 上传到S3失败:", uploadError); + return NextResponse.json( + { + error: "图片上传到存储桶失败", + detail: uploadError instanceof Error ? uploadError.message : "未知错误" + }, + { status: 500 } + ); + } + + // 保存到数据库 + try { + const [savedImage] = await db.insert(generatedImage).values({ + userId, + chatId: chatId || null, + messageId: messageId || null, + thumbnailUrl, + originalUrl, + imageSize: `${config.backupScale}x放大`, + prompt: prompt || "Real-ESRGAN图片变清晰处理", + modelId: `realesrgan-${config.backupModel}`, + }).returning(); + console.log(`[Enhance-RealESRGAN] ✅ 保存到数据库成功,ID: ${savedImage.id}`); + } catch (dbError) { + console.error("[Enhance-RealESRGAN] ⚠️ 保存到数据库失败:", dbError); + // 继续返回成功,图片已处理并上传 + } + + // 返回结果 + return NextResponse.json({ + success: true, + image_base64: result.imageBase64, + thumbnail_url: thumbnailUrl, + original_url: originalUrl, + processing_time: result.processingTime, + api_type: "realesrgan", + }); + + } catch (error) { + console.error("[Enhance-RealESRGAN] ❌ 处理失败:", error); + return NextResponse.json( + { + error: "Real-ESRGAN变清晰处理失败", + detail: error instanceof Error ? error.message : "未知错误", + }, + { status: 500 } + ); + } + } + + // ========== 主API处理路径 ========== // 3. 调用变清晰API(使用异步模式,根据官方示例) - console.log("[Enhance API] 开始准备API调用..."); - const apiUrl = config.apiUrl.endsWith("/") - ? config.apiUrl.slice(0, -1) - : config.apiUrl; + console.log("[Enhance API] 开始准备主API调用..."); + const apiUrl = config.apiUrl!.endsWith("/") + ? config.apiUrl!.slice(0, -1) + : config.apiUrl!; const enhanceUrl = `${apiUrl}/api/tasks/visual/scale`; console.log("[Enhance API] API URL:", enhanceUrl); @@ -283,7 +526,7 @@ export async function POST(request: NextRequest) { formData.append("return_type", "2"); // 返回base64 const headers: Record = { - "X-API-KEY": config.apiKey, + "X-API-KEY": config.apiKey!, ...formData.getHeaders(), // 包含Content-Type和boundary }; @@ -418,7 +661,7 @@ export async function POST(request: NextRequest) { // 4. 轮询获取任务结果(根据官方示例) console.log(`[Enhance] 开始轮询任务结果,task_id: ${taskId}`); - const taskResult = await pollTaskResult(apiUrl, config.apiKey, taskId); + const taskResult = await pollTaskResult(apiUrl, config.apiKey!, taskId); const finalProcessingTime = (Date.now() - startTime) / 1000; diff --git a/scripts/fix-high-cpu.sh b/scripts/fix-high-cpu.sh index 42cd9fe..e6fdd78 100755 --- a/scripts/fix-high-cpu.sh +++ b/scripts/fix-high-cpu.sh @@ -84,3 +84,5 @@ echo "" + + diff --git a/系统性能诊断与解决方案.md b/系统性能诊断与解决方案.md index a383c0b..7c3fd7a 100644 --- a/系统性能诊断与解决方案.md +++ b/系统性能诊断与解决方案.md @@ -137,3 +137,5 @@ pmset -g + +