# 文件路径: inventory-backend/app/api/v1/common/upload.py import os import uuid from flask import Blueprint, request, jsonify, send_from_directory # 定义蓝图 upload_bp = Blueprint('upload', __name__) # ========================================================= # 配置上传路径 (核心修改:确保路径绝对准确) # ========================================================= # 向上寻找直到找到 inventory-backend 目录,或者默认为当前文件的上级目录的...上级 # 这种方式比数 dirname 层级更稳健 def get_project_root(): """获取项目根目录 inventory-backend""" current_path = os.path.abspath(__file__) # 循环向上查找,直到找到名为 inventory-backend 的目录 # 如果你的根目录名字不是 inventory-backend,请修改这里的判断逻辑 # 或者直接使用相对路径回退 5 层: api/v1/common -> app -> inventory-backend base = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(current_path))))) return base BASE_DIR = get_project_root() UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads') # 允许上传的文件后缀 ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp'} def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def ensure_upload_folder_exists(): if not os.path.exists(UPLOAD_FOLDER): try: os.makedirs(UPLOAD_FOLDER) print(f"✅ [Upload] 目录创建成功: {UPLOAD_FOLDER}") except Exception as e: print(f"❌ [Upload] 目录创建失败: {e}") # ------------------------------------------------------------------ # 1. 文件上传接口 # URL: /api/v1/common/upload (POST) # ------------------------------------------------------------------ @upload_bp.route('/upload', methods=['POST']) def upload_file(): ensure_upload_folder_exists() if 'file' not in request.files: return jsonify({"code": 400, "msg": "未找到文件部分"}), 400 file = request.files['file'] if file.filename == '': return jsonify({"code": 400, "msg": "未选择文件"}), 400 if file and allowed_file(file.filename): try: ext = file.filename.rsplit('.', 1)[1].lower() new_filename = f"{uuid.uuid4().hex}.{ext}" save_path = os.path.join(UPLOAD_FOLDER, new_filename) file.save(save_path) print(f"💾 [Upload] 文件已保存: {save_path}") # 生成访问 URL # 这里的路径必须与 __init__.py 中注册的 url_prefix + 路由匹配 file_url = f"/api/v1/common/files/{new_filename}" return jsonify({ "code": 200, "msg": "上传成功", "data": { "url": file_url, "filename": new_filename } }) except Exception as e: print(f"❌ [Upload] 保存异常: {e}") return jsonify({"code": 500, "msg": "文件保存失败"}), 500 return jsonify({"code": 400, "msg": "不支持的文件格式"}), 400 # ------------------------------------------------------------------ # 2. 静态文件访问接口 (回显) # URL: /api/v1/common/files/ # ------------------------------------------------------------------ @upload_bp.route('/files/') def uploaded_file(filename): # 打印日志帮助调试 404 问题 full_path = os.path.join(UPLOAD_FOLDER, filename) if not os.path.exists(full_path): print(f"❌ [File Access] 文件未找到: {full_path}") return jsonify({"code": 404, "msg": "文件不存在"}), 404 return send_from_directory(UPLOAD_FOLDER, filename) # ------------------------------------------------------------------ # 3. 文件删除接口 (同步删除物理文件) # URL: /api/v1/common/files/ (DELETE) # ------------------------------------------------------------------ @upload_bp.route('/files/', methods=['DELETE']) def delete_file(filename): try: # 安全处理文件名 safe_filename = os.path.basename(filename) file_path = os.path.join(UPLOAD_FOLDER, safe_filename) print(f"🗑️ [Delete] 尝试删除文件: {file_path}") if os.path.exists(file_path): os.remove(file_path) print(f"✅ [Delete] 文件删除成功") return jsonify({"code": 200, "msg": "文件已删除"}) else: print(f"⚠️ [Delete] 文件不存在,无需删除") # 即使文件不存在也返回成功,保证前端流程继续 return jsonify({"code": 200, "msg": "文件不存在或已删除"}) except Exception as e: print(f"❌ [Delete] 删除异常: {e}") return jsonify({"code": 500, "msg": f"删除失败: {str(e)}"}), 500