feat: 删除对话时要oss删除文件

This commit is contained in:
肖应宇 2026-03-09 11:10:40 +08:00
parent fe4ee53c38
commit b6ee6c949b
4 changed files with 170 additions and 5 deletions

View File

@ -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,
}

View File

@ -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()

View File

@ -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 <路径>
# ────────────────────────────────────────────────────────────────

View File

@ -196,7 +196,7 @@ async function handleSend(
//
if (!currentConversation.value) {
chatStore.createConversation();
await chatStore.createConversation();
}
//