Files

72 lines
2.7 KiB
Python

"""Attachment validation, storage and retention without request parsing."""
import os
import secrets
import re
import time
from datetime import datetime
from urllib.parse import unquote
from .config import get_setting
def cleanup_uploads():
"""Keep attachments referenced by chat history; expire unused uploads after 7 days."""
try:
udir = get_setting('UPLOAD_DIR')
if not os.path.isdir(udir):
return
from .db import connect
connection = connect()
try:
referenced = set()
rows = connection.execute("SELECT content FROM messages WHERE content LIKE '%/uploads/%' "
"UNION ALL SELECT content FROM human_messages WHERE content LIKE '%/uploads/%'")
for (content,) in rows:
referenced.update(unquote(name) for name in re.findall(r'/uploads/([A-Za-z0-9_.%-]+)', content or ''))
finally:
connection.close()
cutoff = time.time() - 7 * 86400
for f in os.listdir(udir):
if f in referenced:
continue
fp = os.path.join(udir, f)
if os.path.isfile(fp) and os.path.getmtime(fp) < cutoff:
os.remove(fp)
except Exception as e:
print(f'[清理上传文件失败] {e}')
def upload_file(files):
"""上传附件:保存到 uploads 目录,返回文件名与访问地址"""
if 'file' not in files:
return ({'ok': False, 'error': '没有文件'}, 400)
f = files['file']
if not f or not f.filename:
return ({'ok': False, 'error': '文件名为空'}, 400)
ext = os.path.splitext(f.filename)[1].lower()
if ext not in get_setting('ALLOWED_EXT'):
return ({'ok': False, 'error': f'不支持的格式 {ext},仅支持图片/PDF/TXT/CSV'}, 400)
f.seek(0, os.SEEK_END)
size = f.tell()
f.seek(0)
if size > get_setting('MAX_UPLOAD_MB') * 1024 * 1024:
return ({'ok': False, 'error': f"文件超过 {get_setting('MAX_UPLOAD_MB')}MB"}, 400)
os.makedirs(get_setting('UPLOAD_DIR'), exist_ok=True)
safe_name = secrets.token_hex(16) + ext
f.save(os.path.join(get_setting('UPLOAD_DIR'), safe_name))
from .db import connect, now_str
from .visitor_service import visitor_id
owner = visitor_id()
if owner:
connection = connect()
try:
with connection:
connection.execute('INSERT INTO visitor_uploads(filename,visitor_id,ctime) VALUES(?,?,?)',
(safe_name, owner, now_str()))
finally:
connection.close()
is_image = ext in ('.png', '.jpg', '.jpeg', '.gif', '.webp')
return {'ok': True, 'name': f.filename, 'url': f'/uploads/{safe_name}', 'is_image': is_image}