app.py
import os
import uuid
import magic
import logging
from typing import Optional
from flask import Flask, request, flash, redirect, url_for, render_template
from werkzeug.datastructures import FileStorage
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
class Config:
UPLOAD_FOLDER = os.environ.get("UPLOAD_FOLDER", "uploads")
MAX_CONTENT_LENGTH = int(os.environ.get("MAX_CONTENT_LENGTH", 16*1024*1024))
SECRET_KEY = os.environ.get("SECRET_KEY", "5bb02fb755fa57b729f016e20c9c2cfe7e30a585334cc073")
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
ALLOWED_MIMETYPES = {'text/plain', 'image/jpeg', 'image/png', 'image/gif', 'application/pdf'}
EXTENSION_MIME_TYPE = {
'txt': 'text/plain',
'pdf': 'application/pdf',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif'
}
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
# SESSION_COOKIE_SECURE = True
app = Flask(__name__)
app.config.from_object(Config)
logging.basicConfig(level=logging.INFO,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
@app.errorhandler(413)
@app.errorhandler(RequestEntityTooLarge)
def handle_file_too_large(e):
flash('File is to large!MAXIMUM SIZE is 16MB.')
return redirect(url_for('upload_file'))
def allowed_file(filename : str) -> bool:
return ('.' in filename and filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS'])
def detect_mime(file_storage : FileStorage) -> Optional[str]:
try:
file_head = file_storage.stream.read(2048)
file_storage.stream.seek(0)
return magic.from_buffer(file_head, mime=True)
except Exception as e:
logger.warning("MIME检测失败:%s",e)
return None
@app.route('/', methods=['GET', 'POST'])
def hello():
return redirect(url_for('upload_file'))
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
return redirect(url_for('upload_file'))
file :FileStorage = request.files.get('file')
if not file or file.filename == '':
flash('No selected file')
return redirect(url_for('upload_file'))
if not allowed_file(file.filename):
flash('File type not allowed')
return redirect(url_for('upload_file'))
ext = file.filename.rsplit('.',1)[1].lower()
actual_mimetype = detect_mime(file)
if not actual_mimetype:
flash('无法识别文件类型')
return redirect(url_for('upload_file'))
if actual_mimetype not in app.config['ALLOWED_MIMETYPES']:
flash('file type not allowed')
logger.info("拒绝上传,文件名:%s,MIME:%s.",file.filename,actual_mimetype)
return redirect(url_for('upload_file'))
expected_mimetype = app.config['EXTENSION_MIME_TYPE'].get(ext)
if expected_mimetype is not None and expected_mimetype != actual_mimetype:
flash('扩展名和真实类型不同')
logger.info("拒绝上传,文件名:%s,ext:%s,实际:%s",file.filename,ext,actual_mimetype)
return redirect(url_for('upload_file'))
_, original_ext = os.path.splitext(file.filename)
safe_ext =original_ext.lower()
filename = secure_filename(f'{uuid.uuid4().hex}{safe_ext}')
upload_folder = app.config['UPLOAD_FOLDER']
save_path = os.path.join(upload_folder, filename)
os.makedirs(upload_folder, exist_ok=True)
file.save(save_path)
flash(f'File uploaded successfully.It is saved as "{filename}"')
logger.info("文件名为%s,后缀名为%s",filename,actual_mimetype)
return redirect(url_for('upload_file'))
return render_template('index.html')
if __name__ == '__main__':
app.run()
index.html
<!DOCTYPE html>
<html>
<head>
<title>
简单的文件上传
</title>
</head>
<body>
<h1>
在这里上传一个文件
</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{message}}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<form action="{{url_for('upload_file')}}" method="post" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit" name="文件上传"/>
</form>
</body>
</html>
dockerfile
FROM python:3.11-slim
# 环境设置(防止 Python 缓冲 / 生成 pyc)
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# 安装系统依赖:python-magic 需要 libmagic
RUN apt-get update && \
apt-get install -y --no-install-recommends libmagic1 && \
rm -rf /var/lib/apt/lists/*
# 安装 Python 依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目文件
COPY . .
# 创建上传目录
RUN mkdir -p uploads
EXPOSE 8000
# 生产环境不使用 python app.py,而用 gunicorn
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
requirements.txt
Flask==3.0.0
python-magic==0.4.27
gunicorn==21.2.0