Flask 的简单使用

Flask 是什么

Flask 是一个用 Python 编写的轻量级 Web 应用框架。

Flask 基于 WSGI(Web Server Gateway Interface)和 Jinja2 模板引擎,旨在帮助开发者快速、简便地创建 Web 应用。

Flask 被称为"微框架",因为它使用简单的核心,用扩展增加其他功能。

极简的基于 falsk 的 上传小程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import os
from datetime import datetime

from flask import Flask, request, render_template_string, jsonify

UPLOAD_FOLDER = os.path.join(".", "uploads") # 上传文件保存的根目录
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)

app = Flask(__name__)

HTML_TEMPLATE = '''
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 100px;
}
#dropzone {
border: 3px dashed #aaa;
border-radius: 10px;
padding: 150px 50px;
width: 60%;
margin: 0 auto;
background-color: #fafafa;
cursor: pointer;
}
#dropzone.dragover {
background-color: #e0f7fa;
border-color: #00acc1;
}
input[type="file"] {
display: none;
}
#message {
margin-top: 20px;
font-size: 16px;
color: green;
text-align: left;
width: 60%;
margin-left: auto;
margin-right: auto;
}

.new-upload {
margin-top: 1px;
border-radius: 4px;
}
.new-upload.success {
color: #2e7d32;
background-color: #e8f5e9;
}

.new-upload.failure {
color: #c62828;
background-color: #ffebee;
}
</style>
</head>
<body>
<h1>拖拽文件到下方区域,或点击选择文件</h1>
<form id="upload-form" method="post" enctype="multipart/form-data">
<div id="dropzone">
<p>拖拽文件到这里,或点击选择文件</p>
<input type="file" name="file" multiple id="file-input">
</div>
<div id="message"></div>
</form>

<script>
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const message = document.getElementById('message');

dropzone.addEventListener('click', () => {
fileInput.click();
});

fileInput.addEventListener('change', () => {
uploadFiles(fileInput.files);
});

dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('dragover');
});

dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('dragover');
});

dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('dragover');
uploadFiles(e.dataTransfer.files);
});

function uploadFiles(files) {
// 移除旧的上传样式(恢复默认)
document.querySelectorAll('.new-upload').forEach(el => {
el.classList.remove('new-upload', 'success', 'failure');
});

const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('file', files[i]);
}
fetch('/', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
data.results.forEach(res => {
let className = 'new-upload';
if (res.includes('上传成功')) {
className += ' success';
} else if (res.includes('上传失败')) {
className += ' failure';
}
const div = document.createElement('div');
div.className = className;
div.innerHTML = res;
message.appendChild(div);
});
fileInput.value = '';
})
.catch(error => {
const div = document.createElement('div');
div.className = 'new-upload failure';
div.innerHTML = '上传失败: ' + error;
message.appendChild(div);
});
}
</script>
</body>
</html>
'''


@app.route('/', methods=['GET', 'POST'])
def upload_files():
if request.method == 'POST':
files = request.files.getlist('file')
if not files or files[0].filename == '':
return jsonify({'results': ['没有选择文件。']})

today = datetime.now().strftime('%Y-%m-%d')
today_folder = os.path.join(UPLOAD_FOLDER, today)
if not os.path.exists(today_folder):
os.makedirs(today_folder)

blacklist_extensions = ['exe', 'bat', 'sh', 'py', 'js', 'vbs', "kts", "avi", "mp4", "mkv", "java", "ts", "php"]
MAX_FILE_SIZE = 20 * 1024 * 1024 # 20MB
MAX_TOTAL_SIZE = 30 * 1024 * 1024 # 30MB

# 计算总文件大小
total_size = 0
for file in files:
if file:
file.seek(0, os.SEEK_END)
total_size += file.tell()
file.seek(0)

if total_size > MAX_TOTAL_SIZE:
return jsonify({'results': ['总上传大小超过 30MB 限制,请减少文件数量或大小']})

results = []
for file in files:
if file:
# 检查文件大小
file.seek(0, os.SEEK_END)
file_size = file.tell()
file.seek(0)
if file_size > MAX_FILE_SIZE:
results.append(f'{file.filename} 上传失败:文件大小超过 20MB 限制')
continue
# 检查黑名单后缀
ext = os.path.splitext(file.filename)[1].lower().lstrip('.')
if ext in blacklist_extensions:
results.append(f'{file.filename} 上传失败:禁止上传的文件类型')
continue
try:
file_path = os.path.join(today_folder, file.filename)
if os.path.exists(file_path):
base, ext = os.path.splitext(file.filename)
timestamp = datetime.now().strftime('%Y-%m-%d %H-%M-%S')
new_name = f"{base} {timestamp}{ext}"
file_path = os.path.join(today_folder, new_name)
print("filePath =", file_path)
file.save(file_path)
print("filePath saved =", file_path)
results.append(f'{os.path.basename(file_path)} 上传成功')
except Exception as e:
results.append(f'{file.filename} 上传失败: {str(e)}')
print("error =", e)
return jsonify({'results': results})
return render_template_string(HTML_TEMPLATE)


if __name__ == '__main__':
app.run(host='0.0.0.0')

应用部署

gunicorn 不支持在 Windows 系统上直接运行

Gunicorn(Green Unicorn)是一个基于 Python 的 WSGI(Web 服务器网关接口)HTTP 服务器,用于部署 Python Web 应用程序。它在 Unix 系统上表现出色,但官方并不支持在 Windows 上运行。

如果不想使用 WSL,可以考虑使用其他WSGI服务器,如 Waitress。Waitress 是一个纯 Python 编写的 WSGI 服务器,可以在Windows上运行,并且性能与Gunicorn相当。

打开命令提示符或 PowerShell,运行以下命令以安装 Waitress: pip install waitress

使用方法

1
waitress-serve --listen=0.0.0.0:80 app:app