简介
基于flask写的局域网传输文件超快,也可以通过内网穿透实时给外网上传下载。 默认的地址端口号: http://当前ip地址:5001
文件及目录结构
首先创建目录和文件:
.
├── files
│ └── 这里是上传文件存放处.txt
├── templates
│ └── index.html
└── run.py在run.py中添加以下代码:
from flask import Flask, request, send_from_directory, jsonify, abort, render_template
import os
import socket
import qrcode
from colorama import init, Fore
from PIL import Image
import psutil
# 初始化 Colorama
init(autoreset=True)
app = Flask(__name__)
# 设置文件存储的根目录
FILE_DIRECTORY = os.path.join(os.getcwd(), 'files')
@app.route('/')
def index():
# 使用 render_template 渲染 index.html
return render_template('index.html')
# 设置文件上传和下载的路由
@app.route('/upload', methods=['POST'])
def upload_file():
# 检查是否有文件在请求中
if 'file' not in request.files:
return jsonify({'error': '无文件'}), 400
files = request.files.getlist('file') # 获取所有上传的文件列表
for file in files:
if file.filename == '':
return jsonify({'error': '没有选择文件'}), 400
if file:
filename = file.filename
# 检查文件是否已存在,并添加序号
counter = 1
file_path = os.path.join(FILE_DIRECTORY, filename)
while os.path.exists(file_path):
file_name, file_ext = os.path.splitext(filename)
new_filename = f"{file_name}_{counter}{file_ext}"
file_path = os.path.join(FILE_DIRECTORY, new_filename)
counter += 1
file.save(file_path)
return jsonify({'message': '文件上传成功'}), 200
@app.route('/download/<filename>')
def download_file(filename):
# 拼接完整的文件路径
file_path = os.path.join(FILE_DIRECTORY, filename)
# 检查文件是否存在
if not os.path.isfile(file_path):
print(f"文件不存在: {file_path}") # 打印日志
abort(404) # 如果文件不存在,返回 404 错误
return send_from_directory(FILE_DIRECTORY, filename, as_attachment=True)
@app.route('/list')
def list_files():
# 获取请求的目录路径
requested_path = request.args.get('path', '')
full_path = os.path.join(FILE_DIRECTORY, requested_path)
# 确保请求的路径是存在的目录
if not os.path.isdir(full_path):
return jsonify({'error': '目录不存在'}), 404
# 遍历目录
files = []
for item in os.listdir(full_path):
item_path = os.path.join(full_path, item)
if os.path.isfile(item_path):
files.append(item)
# 返回文件列表
return jsonify(files)
'''
获取局域网 IP 地址
@return: 局域网 IP 地址
'''
def get_lan_ip():
try:
# 获取所有网络接口的详细信息
interfaces = psutil.net_if_addrs()
# 遍历接口,寻找名称中包含"WLAN"的接口
for interface_name, addrs in interfaces.items():
if "WLAN" in interface_name:
# 遍历地址信息,寻找IPv4地址
for addr in addrs:
if addr.family == socket.AF_INET: # 使用socket的AF_INET来检查IPv4地址
return addr.address
# 如果没有找到非回环地址,返回 '127.0.0.1'
return '127.0.0.1'
except Exception as e:
# 如果发生异常,打印错误信息并返回 '127.0.0.1'
print(f"获取局域网 IP 地址时发生错误: {e}")
return '127.0.0.1'
'''
创建二维码
@param url: 二维码链接
@param filename: 二维码文件名
'''
def create_qr_code(url, filename):
qr = qrcode.QRCode(
version=1,
box_size=10,
border=5
)
qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# 检查文件是否存在,如果存在则覆盖
if os.path.exists(filename):
os.remove(filename) # 删除已存在的文件
img.save(filename)
'''
打印二维码到控制台
@param data: 二维码数据
@param fill_char: 填充字符
@param back_char: 背景字符
@param fill_color: 填充颜色
@param back_color: 背景颜色
'''
def print_qr_code_console(data, fill_char='█', back_char=' ', box_size=1, version=1):
qr = qrcode.QRCode(
version=version,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=box_size,
border=1
)
qr.add_data(data)
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
pil_img = qr_img.convert('1')
for y in range(pil_img.size[1]):
for x in range(pil_img.size[0]):
color = pil_img.getpixel((x, y))
char = fill_char if color else back_char
print(Fore.BLUE + char, end='')
print()
if __name__ == '__main__':
# 获取局域网 IP 地址
local_ip = get_lan_ip()
# 构建访问 Flask 应用的 URL
flask_url = f"http://{local_ip}:5001"
print("获取的url是:"+ flask_url)
# 定义二维码图片的文件名
qr_code_filename = 'qr_code.png'
# 生成并保存二维码图片
create_qr_code(flask_url, qr_code_filename)
print_qr_code_console(flask_url)
app.run(host='0.0.0.0', port=5001) # 监听所有可用的网络接口
- index.html文件内容如下:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文件传输页面</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* 默认背景图片(用于桌面) */
html, body {
height: 100%;
margin: 0;
padding: 0;
overflow-x: hidden; /* 防止横向滚动条 */
background-image: url('https://s2.loli.net/2024/08/16/PoYAIHT8XzgUjVm.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed; /* 背景固定 */
}
body {
color: #fff;
font-family: Arial, sans-serif;
padding-top: 120px; /* 调整 padding-top 值 */
}
header {
display: flex;
align-items: center; /* 垂直居中 Logo 和标题 */
justify-content: flex-start; /* 将 Logo 和标题对齐到左边 */
padding: 20px 40px; /* 调整 padding */
background: transparent; /* 去掉背景色 */
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0); /* 去掉阴影 */
border-bottom: none; /* 去掉底部黑线 */
height: 150px; /* 调整 header 高度 */
}
#logo {
height: 150px; /* 调整 Logo 的高度 */
width: auto; /* 保持比例 */
margin-right: 20px; /* Logo 和标题之间的间距 */
}
h1 {
margin: 0;
color: #fff;
font-size: 1.5em;
text-align: left; /* 标题左对齐 */
}
/* 使内容区域在 Logo 下方并靠左对齐 */
#content {
display: flex;
flex-direction: column;
align-items: flex-start; /* 内容左对齐 */
padding: 20px;
color: #fff;
text-align: left; /* 内容左对齐 */
margin-left: 20px; /* 与左侧边距对齐 */
}
#file-list {
margin-bottom: 20px;
}
#overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
color: #fff;
text-align: center;
padding-top: 100px;
}
.overlay-content {
display: inline-block;
background: #333;
padding: 20px;
border-radius: 8px;
}
.progress-bar-container {
background: #444;
border-radius: 8px;
overflow: hidden;
margin-top: 10px;
width: 100%;
height: 20px;
}
#progress-bar {
background: #76c7c0;
height: 100%;
width: 0;
transition: width 0.3s ease;
}
#upload-status {
margin-top: 10px;
font-size: 14px;
}
input[type="file"] {
margin-bottom: 10px;
}
button {
background: #76c7c0;
border: none;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background: #65a8a6;
}
/* 响应式设计 */
@media (max-width: 768px) {
html, body {
background-image: url('https://s2.loli.net/2024/08/17/bfZWLdE8RGqgYVp.jpg'); /* 手机背景图片 */
background-attachment: fixed; /* 背景随页面滚动 */
}
header {
padding: 10px; /* 在小屏幕上减少 padding */
height: auto; /* 自适应高度 */
box-shadow: none; /* 去掉阴影 */
}
#logo {
height: 80px; /* 调整 Logo 高度 */
}
h1 {
font-size: 1.2em; /* 调整标题大小 */
}
#content {
padding: 10px;
margin-left: 0; /* 去掉左边距 */
text-align: center; /* 中心对齐内容 */
}
#file-list {
margin-bottom: 10px; /* 减少底部间距 */
}
input[type="file"] {
width: 100%; /* 输入框宽度100% */
margin-bottom: 15px; /* 增加底部间距 */
}
button {
width: 100%; /* 按钮宽度100% */
padding: 15px; /* 增加 padding */
font-size: 1.1em; /* 增加字体大小 */
}
.overlay-content {
padding: 10px; /* 减少 padding */
}
.progress-bar-container {
height: 15px; /* 减少进度条高度 */
}
#progress-bar {
height: 100%;
}
#upload-status {
font-size: 12px; /* 调整上传状态字体大小 */
}
}
</style>
</head>
<body>
<header>
<img src="https://s2.loli.net/2024/08/17/PhertXdzYC34ONm.png" alt="Logo" id="logo">
<h1>文件传输页面</h1>
</header>
<div id="content">
<h2>文件列表</h2>
<div id="file-list"></div>
<h2>上传文件</h2>
<input type="file" id="file-upload" multiple />
<button id="upload-button">上传</button>
</div>
<div id="overlay">
<div class="overlay-content">
<p>正在上传的文件</p>
<div class="progress-bar-container">
<div id="progress-bar" class="progress-bar"></div>
</div>
<div id="upload-status">
<span id="percentage">0%</span> -
<span id="speed">0 KB/s</span>
</div>
</div>
</div>
<script>
function showOverlay() {
document.getElementById('overlay').style.display = 'block';
}
function hideOverlay() {
document.getElementById('overlay').style.display = 'none';
}
function listFiles() {
$.ajax({
url: '/list',
type: 'GET',
success: function (files) {
var fileList = files;
var filesHtml = fileList.map(function (file) {
var icon = 'fas fa-file';
if (file.endsWith('.pdf')) icon = 'fas fa-file-pdf';
if (file.endsWith('.docx')) icon = 'fas fa-file-word';
if (file.endsWith('.xlsx')) icon = 'fas fa-file-excel';
if (file.endsWith('.jpg') || file.endsWith('.png')) icon = 'fas fa-file-image';
if (file.endsWith('.txt')) icon = 'fas fa-file-alt';
if (file.endsWith('.zip')) icon = 'fas fa-file-archive';
if (file.endsWith('.csv')) icon = 'fas fa-file-csv';
return '<div><i class="' + icon + '"></i><a href="/download/' + encodeURIComponent(file) + '">' + file + '</a></div>';
}).join('');
$('#file-list').html(filesHtml);
},
error: function () {
alert('无法获取文件列表');
}
});
}
function updateProgressBar(uploadCount, totalFiles, percentComplete, speed) {
document.getElementById('progress-bar').style.width = percentComplete + '%';
document.getElementById('percentage').innerText = Math.round(percentComplete) + '%';
document.getElementById('speed').innerText = speed + ' KB/s';
}
function uploadFile() {
showOverlay();
var fileInput = document.getElementById('file-upload');
var files = fileInput.files;
var totalFiles = files.length;
var uploadCount = 0;
var startTime, endTime;
if (totalFiles === 0) {
alert('请选择文件');
hideOverlay();
return;
}
for (var i = 0; i < files.length; i++) {
(function(file) {
var formData = new FormData();
formData.append('file', file);
startTime = Date.now();
$.ajax({
url: '/upload',
type: 'POST',
data: formData,
contentType: false,
processData: false,
xhr: function() {
var xhr = new XMLHttpRequest();
xhr.upload.onprogress = function(event) {
if (event.lengthComputable) {
var percentComplete = (event.loaded / event.total) * 100;
var currentTime = Date.now();
var elapsedTime = (currentTime - startTime) / 1000; // seconds
var speed = (event.loaded / 1024) / elapsedTime; // KB/s
updateProgressBar(uploadCount, totalFiles, percentComplete, speed.toFixed(2));
}
};
return xhr;
},
success: function(data) {
console.log('成功:', data);
},
error: function(xhr) {
console.error('错误:', xhr.responseText);
},
complete: function() {
endTime = Date.now();
var totalTime = (endTime - startTime) / 1000; // seconds
var fileSizeKB = file.size / 1024;
var uploadSpeed = (fileSizeKB / totalTime).toFixed(2); // KB/s
uploadCount++;
var percentComplete = (uploadCount / totalFiles) * 100;
updateProgressBar(uploadCount, totalFiles, percentComplete, uploadSpeed);
if (uploadCount === totalFiles) {
alert('所有文件上传成功');
listFiles();
hideOverlay();
}
}
});
})(files[i]);
}
}
$(document).ready(function() {
listFiles();
$('#upload-button').on('click', uploadFile);
});
</script>
</body>
</html>
使用
修改index.html里面的电脑背景图片手机背景与logo图片为自己的 然后下载对应的python包 运行run.py即可 局域网内的人可以通过输出的连接/扫描生成的二维码即可上传下载文件
如果需要限制文件大小上传可以在run.py中的app = Flask(name)下面添加代码:
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制上传文件大小为16MB
- 如果需要限制文件类型上传可以在run.py中的app = Flask(name)下面添加代码:
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'} # 允许上传的文件类型
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
然后在upload()函数中添加代码:
if not allowed_file(file.filename):
flash('不允许上传该类型的文件')
return redirect(request.url)