局域网快速传输


简介

基于flask写的局域网传输文件超快,也可以通过内网穿透实时给外网上传下载。 默认的地址端口号: http://当前ip地址:5001

文件及目录结构

  1. 首先创建目录和文件:
    .
    ├── files
    │ └── 这里是上传文件存放处.txt
    ├── templates
    │ └── index.html
    └── run.py

  2. 在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)  # 监听所有可用的网络接口
  1. 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>

使用

  1. 修改index.html里面的电脑背景图片手机背景与logo图片为自己的 然后下载对应的python包 运行run.py即可 局域网内的人可以通过输出的连接/扫描生成的二维码即可上传下载文件

  2. 如果需要限制文件大小上传可以在run.py中的app = Flask(name)下面添加代码:

app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 限制上传文件大小为16MB
  1. 如果需要限制文件类型上传可以在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)

文章作者: 孙尾苏
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 孙尾苏 !
评论
  目录