feat: AI套图分层方案 + Gemini集成 - 4种图案类型处理 + 正片叠底 + 宽高比 + 模型选择
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
154
Server/app/core/qiniu_storage.py
Normal file
154
Server/app/core/qiniu_storage.py
Normal file
@@ -0,0 +1,154 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
七牛云对象存储工具
|
||||
"""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
from qiniu import Auth, put_data, BucketManager
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# ==================== 配置 ====================
|
||||
# 从 settings 读取(通过 pydantic-settings 加载 .env)
|
||||
|
||||
QINIU_ACCESS_KEY = settings.QINIU_ACCESS_KEY
|
||||
QINIU_SECRET_KEY = settings.QINIU_SECRET_KEY
|
||||
QINIU_BUCKET = settings.QINIU_BUCKET
|
||||
QINIU_DOMAIN = settings.QINIU_DOMAIN # 例如: http://cdn.example.com
|
||||
|
||||
# 是否启用七牛云存储
|
||||
QINIU_ENABLED = bool(QINIU_ACCESS_KEY and QINIU_SECRET_KEY and QINIU_BUCKET and QINIU_DOMAIN)
|
||||
|
||||
|
||||
class QiniuStorage:
|
||||
"""七牛云存储工具类"""
|
||||
|
||||
def __init__(self):
|
||||
self.enabled = QINIU_ENABLED
|
||||
if self.enabled:
|
||||
self.auth = Auth(QINIU_ACCESS_KEY, QINIU_SECRET_KEY)
|
||||
self.bucket = QINIU_BUCKET
|
||||
self.domain = QINIU_DOMAIN.rstrip('/')
|
||||
print(f"✅ 七牛云存储已启用,Bucket: {self.bucket}")
|
||||
else:
|
||||
self.auth = None
|
||||
self.bucket = None
|
||||
self.domain = None
|
||||
print("⚠️ 七牛云存储未配置,图片将使用 base64 返回")
|
||||
|
||||
def upload_base64(
|
||||
self,
|
||||
base64_data: str,
|
||||
key_prefix: str = "plt",
|
||||
user_id: Optional[int] = None
|
||||
) -> Tuple[bool, str]:
|
||||
"""
|
||||
上传 base64 图片到七牛云
|
||||
|
||||
Args:
|
||||
base64_data: base64 编码的图片数据(可带或不带 data:image/png;base64, 前缀)
|
||||
key_prefix: 文件名前缀
|
||||
user_id: 用户ID(用于分目录)
|
||||
|
||||
Returns:
|
||||
(success, url_or_error): 成功返回 URL,失败返回错误信息
|
||||
"""
|
||||
if not self.enabled:
|
||||
return False, "七牛云存储未启用"
|
||||
|
||||
try:
|
||||
# 去掉 base64 前缀
|
||||
if ',' in base64_data:
|
||||
base64_data = base64_data.split(',')[1]
|
||||
|
||||
# 解码
|
||||
image_data = base64.b64decode(base64_data)
|
||||
|
||||
# 生成文件名: plt/2024/02/05/u123/时间戳_哈希.png
|
||||
now = datetime.now()
|
||||
date_path = now.strftime("%Y/%m/%d")
|
||||
timestamp = now.strftime("%H%M%S") # 时分秒
|
||||
content_hash = hashlib.md5(image_data).hexdigest()[:8]
|
||||
|
||||
if user_id:
|
||||
key = f"{key_prefix}/{date_path}/u{user_id}/{timestamp}_{content_hash}.png"
|
||||
else:
|
||||
key = f"{key_prefix}/{date_path}/{timestamp}_{content_hash}.png"
|
||||
|
||||
# 生成上传凭证
|
||||
token = self.auth.upload_token(self.bucket, key, 3600)
|
||||
|
||||
# 上传
|
||||
ret, info = put_data(token, key, image_data)
|
||||
|
||||
if info.status_code == 200:
|
||||
url = f"{self.domain}/{key}"
|
||||
return True, url
|
||||
else:
|
||||
print(f"[七牛云] 上传失败: status={info.status_code}, error={info.error}, text={info.text_body}")
|
||||
return False, f"上传失败: {info.error}"
|
||||
|
||||
except Exception as e:
|
||||
print(f"[七牛云] 上传异常: {str(e)}")
|
||||
return False, f"上传异常: {str(e)}"
|
||||
|
||||
def upload_batch(
|
||||
self,
|
||||
images: list, # [(base64_data, name), ...]
|
||||
key_prefix: str = "plt",
|
||||
user_id: Optional[int] = None
|
||||
) -> list:
|
||||
"""
|
||||
批量上传图片
|
||||
|
||||
Returns:
|
||||
[(success, url_or_base64, name), ...]
|
||||
"""
|
||||
results = []
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for base64_data, name in images:
|
||||
if self.enabled:
|
||||
success, result = self.upload_base64(base64_data, key_prefix, user_id)
|
||||
if success:
|
||||
results.append((True, result, name))
|
||||
success_count += 1
|
||||
else:
|
||||
# 上传失败,回退到 base64
|
||||
print(f"[七牛云] 上传失败 {name}: {result}")
|
||||
results.append((False, base64_data, name))
|
||||
fail_count += 1
|
||||
else:
|
||||
# 未启用,返回原始 base64
|
||||
results.append((False, base64_data, name))
|
||||
|
||||
if fail_count > 0:
|
||||
print(f"[七牛云] 批量上传结果: 成功 {success_count}, 失败 {fail_count}")
|
||||
|
||||
return results
|
||||
|
||||
def delete(self, key: str) -> bool:
|
||||
"""删除文件"""
|
||||
if not self.enabled:
|
||||
return False
|
||||
|
||||
try:
|
||||
bucket_manager = BucketManager(self.auth)
|
||||
ret, info = bucket_manager.delete(self.bucket, key)
|
||||
return info.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_url(self, key: str) -> str:
|
||||
"""获取文件 URL"""
|
||||
if not self.enabled or not key:
|
||||
return ""
|
||||
return f"{self.domain}/{key}"
|
||||
|
||||
|
||||
# 全局实例
|
||||
qiniu_storage = QiniuStorage()
|
||||
Reference in New Issue
Block a user