feat: 删除对话时要oss删除文件
This commit is contained in:
parent
fe4ee53c38
commit
b6ee6c949b
|
|
@ -30,7 +30,7 @@ class ModelInfo:
|
|||
"maxTokens": self.max_tokens,
|
||||
"provider": self.provider,
|
||||
"supports_thinking": self.supports_thinking,
|
||||
"supports_web_Search": self.supports_web_search,
|
||||
"supports_web_search": self.supports_web_search,
|
||||
"supports_vision": self.supports_vision,
|
||||
"supports_files": self.supports_files,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,8 +65,38 @@ async def save_conversation_handler(data: dict):
|
|||
|
||||
|
||||
async def delete_conversation_handler(conversation_id: str):
|
||||
"""删除对话处理器"""
|
||||
"""删除对话处理器(同时删除关联的 OSS 文件)"""
|
||||
db = get_db()
|
||||
|
||||
# 先获取会话数据,提取 OSS 文件 URL
|
||||
conversation = db.get_conversation(conversation_id)
|
||||
if not conversation:
|
||||
raise HTTPException(status_code=404, detail="对话不存在")
|
||||
|
||||
# 提取所有 OSS 文件 URL
|
||||
oss_urls = _extract_oss_urls_from_conversation(conversation)
|
||||
|
||||
# 删除 OSS 文件
|
||||
if oss_urls:
|
||||
try:
|
||||
from utils.oss_uploader import delete_files, extract_object_key_from_url
|
||||
|
||||
object_keys = []
|
||||
for url in oss_urls:
|
||||
key = extract_object_key_from_url(url)
|
||||
if key:
|
||||
object_keys.append(key)
|
||||
|
||||
if object_keys:
|
||||
result = delete_files(object_keys)
|
||||
log_info(f"[删除会话] OSS 文件清理结果: 删除 {len(result['deleted'])} 个, 失败 {len(result['failed'])} 个")
|
||||
if result['failed']:
|
||||
log_error(f"[删除会话] OSS 文件删除失败: {result['failed']}")
|
||||
except Exception as e:
|
||||
log_error(f"[删除会话] OSS 文件删除异常: {e}")
|
||||
# 继续删除会话,即使 OSS 删除失败
|
||||
|
||||
# 删除数据库记录
|
||||
success = db.delete_conversation(conversation_id)
|
||||
if success:
|
||||
return {"success": True, "message": "删除成功"}
|
||||
|
|
@ -74,6 +104,47 @@ async def delete_conversation_handler(conversation_id: str):
|
|||
raise HTTPException(status_code=404, detail="对话不存在")
|
||||
|
||||
|
||||
def _extract_oss_urls_from_conversation(conversation: dict) -> list:
|
||||
"""
|
||||
从会话消息中提取所有 OSS 文件 URL
|
||||
|
||||
消息结构:
|
||||
- content.images: 图片附件列表
|
||||
- content.files: 文件附件列表
|
||||
每个附件包含 url 字段
|
||||
"""
|
||||
urls = []
|
||||
messages = conversation.get("messages", [])
|
||||
|
||||
for message in messages:
|
||||
content = message.get("content")
|
||||
if not content:
|
||||
continue
|
||||
|
||||
# content 可能是字符串(需要解析)或已解析的字典
|
||||
if isinstance(content, str):
|
||||
try:
|
||||
content = json.loads(content)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 提取图片附件
|
||||
images = content.get("images", [])
|
||||
for img in images:
|
||||
url = img.get("url")
|
||||
if url and url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
# 提取文件附件
|
||||
files = content.get("files", [])
|
||||
for f in files:
|
||||
url = f.get("url")
|
||||
if url and url not in urls:
|
||||
urls.append(url)
|
||||
|
||||
return urls
|
||||
|
||||
|
||||
async def update_conversation_handler(conversation_id: str, data: dict):
|
||||
"""部分更新对话处理器"""
|
||||
db = get_db()
|
||||
|
|
|
|||
|
|
@ -57,11 +57,12 @@ def _get_client() -> oss.Client:
|
|||
return oss.Client(cfg)
|
||||
|
||||
|
||||
def _generate_object_key(filename: str, prefix: str = "uploads") -> str:
|
||||
def _generate_object_key(filename: str, prefix: str = "chat-ui") -> str:
|
||||
"""
|
||||
根据文件名生成唯一的 OSS 对象 Key
|
||||
格式: {prefix}/{日期}/{uuid}_{原始文件名}
|
||||
"""
|
||||
# TODO: 需要按用户ID分目录
|
||||
date_str = datetime.now().strftime("%Y%m%d")
|
||||
unique_id = uuid.uuid4().hex[:8]
|
||||
safe_name = Path(filename).name # 只取文件名,去掉路径
|
||||
|
|
@ -80,7 +81,7 @@ def _build_url(object_key: str) -> str:
|
|||
def upload_file(
|
||||
file_path: str,
|
||||
object_key: Optional[str] = None,
|
||||
prefix: str = "uploads",
|
||||
prefix: str = "chat-ui",
|
||||
) -> dict:
|
||||
"""
|
||||
上传本地文件到 OSS
|
||||
|
|
@ -204,6 +205,99 @@ def upload_fileobj(
|
|||
)
|
||||
|
||||
|
||||
def delete_file(object_key: str) -> bool:
|
||||
"""
|
||||
删除 OSS 上的单个文件
|
||||
|
||||
参数:
|
||||
object_key: OSS 对象路径(如 "uploads/20240301/abc123_file.jpg")
|
||||
|
||||
返回:
|
||||
True 表示删除成功,False 表示失败
|
||||
"""
|
||||
try:
|
||||
client = _get_client()
|
||||
result = client.delete_object(
|
||||
oss.DeleteObjectRequest(
|
||||
bucket=OSS_BUCKET_NAME,
|
||||
key=object_key,
|
||||
)
|
||||
)
|
||||
return result.status_code == 204
|
||||
except Exception as e:
|
||||
print(f"[OSS] 删除文件失败: {object_key}, 错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def delete_files(object_keys: list) -> dict:
|
||||
"""
|
||||
批量删除 OSS 上的文件
|
||||
|
||||
参数:
|
||||
object_keys: OSS 对象路径列表
|
||||
|
||||
返回:
|
||||
{
|
||||
"deleted": ["成功删除的 object_key 列表"],
|
||||
"failed": ["删除失败的 object_key 列表"],
|
||||
}
|
||||
"""
|
||||
deleted = []
|
||||
failed = []
|
||||
|
||||
for key in object_keys:
|
||||
if delete_file(key):
|
||||
deleted.append(key)
|
||||
else:
|
||||
failed.append(key)
|
||||
|
||||
return {"deleted": deleted, "failed": failed}
|
||||
|
||||
|
||||
def extract_object_key_from_url(url: str) -> Optional[str]:
|
||||
"""
|
||||
从 OSS URL 中提取 object_key
|
||||
|
||||
参数:
|
||||
url: OSS 文件的完整 URL
|
||||
|
||||
返回:
|
||||
object_key 或 None(如果不是有效的 OSS URL)
|
||||
"""
|
||||
if not url:
|
||||
return None
|
||||
|
||||
# 支持两种 URL 格式:
|
||||
# 1. 自定义域名: OSS_URL_PREFIX/object_key
|
||||
# 2. 默认域名: https://bucket.endpoint/object_key
|
||||
|
||||
try:
|
||||
# 移除查询参数
|
||||
url_path = url.split("?")[0]
|
||||
|
||||
if OSS_URL_PREFIX:
|
||||
# 自定义域名格式
|
||||
prefix = OSS_URL_PREFIX.rstrip("/")
|
||||
if url_path.startswith(prefix):
|
||||
return url_path[len(prefix) + 1:] # +1 去掉开头的 /
|
||||
|
||||
# 默认域名格式: https://bucket.endpoint/object_key
|
||||
endpoint = OSS_ENDPOINT.replace("https://", "").replace("http://", "")
|
||||
default_prefix = f"https://{OSS_BUCKET_NAME}.{endpoint}/"
|
||||
|
||||
if url_path.startswith(default_prefix):
|
||||
return url_path[len(default_prefix):]
|
||||
|
||||
# 也尝试匹配 http 版本
|
||||
http_prefix = f"http://{OSS_BUCKET_NAME}.{endpoint}/"
|
||||
if url_path.startswith(http_prefix):
|
||||
return url_path[len(http_prefix):]
|
||||
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
# 命令行入口:python -m utils.oss_uploader --file <路径>
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ async function handleSend(
|
|||
|
||||
// 如果没有当前对话,创建新对话
|
||||
if (!currentConversation.value) {
|
||||
chatStore.createConversation();
|
||||
await chatStore.createConversation();
|
||||
}
|
||||
|
||||
// 从当前会话中提取历史消息(用于上下文记忆),在添加新消息之前提取
|
||||
|
|
|
|||
Loading…
Reference in New Issue