import os import uuid import mimetypes from flask import current_app from werkzeug.utils import secure_filename # MIME 类型到资源类型的映射 MIME_TYPE_MAP = { # 文本 'text/plain': 'text', 'text/markdown': 'text', 'text/csv': 'text', 'text/html': 'text', 'text/xml': 'text', 'application/json': 'text', 'application/xml': 'text', # 图片 'image/jpeg': 'image', 'image/png': 'image', 'image/gif': 'image', 'image/webp': 'image', 'image/bmp': 'image', 'image/svg+xml': 'image', 'image/x-icon': 'image', # 音频 'audio/mpeg': 'audio', 'audio/wav': 'audio', 'audio/ogg': 'audio', 'audio/flac': 'audio', 'audio/mp4': 'audio', 'audio/aac': 'audio', 'audio/x-ms-wma': 'audio', # 视频 'video/mp4': 'video', 'video/webm': 'video', 'video/x-msvideo': 'video', 'video/x-matroska': 'video', 'video/quicktime': 'video', 'video/x-ms-wmv': 'video', 'video/x-flv': 'video', 'video/mp2t': 'video', } EXT_TYPE_MAP = { 'txt': 'text', 'md': 'text', 'csv': 'text', 'json': 'text', 'xml': 'text', 'log': 'text', 'html': 'text', 'htm': 'text', 'jpg': 'image', 'jpeg': 'image', 'png': 'image', 'gif': 'image', 'webp': 'image', 'bmp': 'image', 'svg': 'image', 'ico': 'image', 'mp3': 'audio', 'wav': 'audio', 'ogg': 'audio', 'flac': 'audio', 'm4a': 'audio', 'aac': 'audio', 'wma': 'audio', 'mp4': 'video', 'webm': 'video', 'avi': 'video', 'mkv': 'video', 'mov': 'video', 'wmv': 'video', 'flv': 'video', 'm4v': 'video', } def guess_resource_type(filename, mime=None): """根据 MIME 或扩展名猜测资源类型""" if mime and mime in MIME_TYPE_MAP: return MIME_TYPE_MAP[mime] ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else '' return EXT_TYPE_MAP.get(ext) def allowed_file(filename): """判断文件扩展名是否在允许列表中""" if '.' not in filename: return False ext = filename.rsplit('.', 1)[1].lower() return ext in current_app.config.get('ALLOWED_TEXT_EXT', set()) | \ current_app.config.get('ALLOWED_IMAGE_EXT', set()) | \ current_app.config.get('ALLOWED_AUDIO_EXT', set()) | \ current_app.config.get('ALLOWED_VIDEO_EXT', set()) def save_uploaded_file(file_obj, resource_type): """ 保存上传的文件到 uploads// 目录。 返回 (filename_on_disk, relative_path, file_size, mime_type) """ original_name = secure_filename(file_obj.filename) ext = original_name.rsplit('.', 1)[-1].lower() if '.' in original_name else '' unique_name = f"{uuid.uuid4().hex}.{ext}" if ext else uuid.uuid4().hex save_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], resource_type) os.makedirs(save_dir, exist_ok=True) save_path = os.path.join(save_dir, unique_name) file_obj.save(save_path) file_size = os.path.getsize(save_path) mime_type = mimetypes.guess_type(original_name)[0] or 'application/octet-stream' # 相对于 static 的路径,供 url_for('static', filename=...) 使用 rel_path = os.path.join('uploads', resource_type, unique_name).replace('\\', '/') return unique_name, rel_path, file_size, mime_type, original_name def delete_resource_file(file_path): """删除资源文件,file_path 是相对于 static 的路径""" if not file_path: return abs_path = os.path.join( current_app.root_path, 'static', file_path.lstrip('/') ) if os.path.exists(abs_path): os.remove(abs_path)