xueyi 发表于 2024-6-16 17:38

局域网文件传输神器电脑、手机无缝对接!

本帖最后由 xueyi 于 2024-6-16 17:43 编辑


# 【软件介绍】
工作需要跨设备文件传输的需求日益增长。为了Android、iPhone以及电脑设备之间的文件互传方便,我写了一款无需安装任意终端都可以使用的局域网文件传输工具。
# 【开发说明】
利用局域网技术,实现快速且稳定的文件传输,无需消耗移动数据。
电脑运行程序即自动搭建服务器,无需复杂的配置或设置。
# 【技术说明】

> Flask:一个轻量级的Web框架,用于快速搭建内网中的HTTP服务器,提供文件上传和下载的Web接口。
> socket:Python标准库中的网络通信库,用于处理底层网络通信,获取局域网IP地址。
> qrcode:用于生成二维码的库,方便移动设备快速访问Web界面。
> colorama:用于在终端或控制台应用程序中添加颜色和样式到文本输出

运行run.py同局域网的电脑访问直接访问` http://[局域网地址]:5001`

# 【效果图】







# 【代码说明】

run.py
```
from flask import Flask, request, send_from_directory, jsonify, abort, render_template
import os
import socket
import qrcode
app = Flask(__name__)
from colorama import init, Fore
# 初始化Colorama
init(autoreset=True)
   
# 设置文件存储的根目录
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(os.path.join(FILE_DIRECTORY, file_path))
            return jsonify({'message': '文件上传成功', 'filename': filename}), 200
    return jsonify({'error': '上传文件时发生错误'}), 500

@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', FILE_DIRECTORY)
    full_path = os.path.join(FILE_DIRECTORY, requested_path)
   
    # 确保请求的路径是存在的目录
    if not os.path.isdir(full_path):
      return jsonify({'error': 'Directory not found'}), 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:
      # 获取主机名
      hostname = socket.gethostname()
      # 获取与主机名关联的所有IP地址信息
      hostname_info = socket.gethostbyname_ex(hostname)
      # 获取所有IP地址列表
      ips = hostname_info
      # 遍历IP地址列表,返回第一个非本地回环地址
      for ip in ips:
            if str(ip).startswith('192.'):
                return ip
      # 如果没有找到非回环地址,返回本地回环地址
      return '127.0.0.1'
    except socket.error as e:
      print(f"Error getting LAN 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")
    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")
   
    # 打印二维码
    for y in range(qr_img.size):
      for x in range(qr_img.size):
            # 根据二维码的黑白部分选择填充字符
            color = qr_img.getpixel((x, y))
            # 根据颜色值选择字符
            char = fill_char if color else back_char            # 使用ANSI颜色代码打印字符
            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(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="en">

<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>
</head>

<body>
    <h1>文件列表</h1>
    <div id="file-list"></div>
    <h2>上传文件</h2>
    <input type="file" id="file-upload" multiple />
    <button>上传</button>
    <!-- 添加一个遮罩层元素 -->
    <div id="overlay" style="display:none;">
      <div class="overlay-content">
            <p>正在上传文件...</p>
            <div class="progress-bar-container">
                <div id="progress-bar" class="progress-bar"></div>
            </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) {
                        return '<div><a href="/download/' + encodeURIComponent(file) + '">' + file + '</a></div>';
                  }).join('');
                  $('#file-list').html(filesHtml);
                },
                error: function () {
                  alert('无法获取文件列表');
                }
            });
      }
      // 更新进度条
      function updateProgressBar(percentComplete, total) {
            var progressBarWidth = percentComplete / total * 100 + '%';
            console.log(progressBarWidth);
            document.getElementById('progress-bar').style.width = progressBarWidth;
      }
      // 上传文件
      function uploadFile() {
            showOverlay(); // 打开遮罩
            var fileInput = document.getElementById('file-upload');
            var files = fileInput.files;
            var totalFiles = files.length; // 总文件数
            var uploadCount = 0; // 设置上传计数器
            // 为每个文件上传创建单独的AJAX请求
            for (var i = 0; i < files.length; i++) {
                var formData = new FormData();
                formData.append('file', files);
                $.ajax({
                  url: '/upload', // 服务器上的路由,用于上传文件
                  type: 'POST',
                  data: formData,
                  contentType: false,
                  processData: false,
                  success: function (data) {
                        console.log(data);
                  },
                  error: function (xhr) {
                        console.error(xhr.responseText);
                  },
                  complete: function () {
                        updateProgressBar(uploadCount, totalFiles);
                        // 所有文件上传完毕后执行的操作
                        if (++uploadCount === totalFiles) {
                            alert('所有文件上传成功');
                            listFiles(); // 刷新文件列表
                            hideOverlay(); // 关闭遮罩
                        }
                  }
                });
            }
      }



      // 页面加载时列出文件
      $(document).ready(function () {
            listFiles();
      });
    </script>

    <style>
      body {
            font-family: 'Arial', sans-serif;
            background: #f7f7f7;
            margin: 0;
            padding: 20px;
            color: #333;
      }

      #overlay {
            position: fixed;
            /* 固定定位,覆盖整个视口 */
            display: none;
            /* 默认不显示 */
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, 0.5);
            /* 黑色背景,半透明 */
            z-index: 2;
            /* 确保遮罩层在其他内容之上 */
      }

      .overlay-content {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            text-align: center;
      }

      h1,
      h2 {
            font-weight: normal;
            margin-top: 0;
      }

      #file-list {
            margin: 20px 0;
            padding: 10px;
            background: #fff;
            border-radius: 5px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }

      #file-list a {
            text-decoration: none;
            color: #337ab7;
      }

      button {
            background-color: #5cb85c;
            color: white;
            border: none;
            padding: 10px 20px;
            margin: 10px 0;
            border-radius: 5px;
            cursor: pointer;
            transition: background-color 0.3s ease;
      }

      button:hover {
            background-color: #4cae4c;
      }

      input {
            margin-bottom: 10px;
      }

      .progress-bar-container {
            width: 100%;
            background-color: #ddd;
            border-radius: 5px;
      }

      .progress-bar {
            height: 20px;
            background-color: #4caf50;
            border-radius: 5px;
            width: 0%;
            /* 初始进度为0% */
            transition: width 0.3s ease;
            /* 平滑过渡效果 */
      }

      /* 响应式设计 */
      @media (max-width: 768px) {
            body {
                padding: 10px;
            }

            .overlay-content {
                width: 90%;
            }
      }
    </style>
</body>

</html>
```

xueyi 发表于 2024-6-17 01:13

黑白夜丶 发表于 2024-6-17 01:09
苹果手机可以用吗?最大可以传输多大的文件呢。

支持,Flask 默认支持 10M
如果需要大文件建议在 app.py 中修改
# 设置最大上传文件大小为 20MB
app.config['MAX_CONTENT_LENGTH'] = 20 * 1024 * 1024

lsgd002 发表于 2024-6-17 09:10

正在学习PY,偶来研究研究。

hk9186 发表于 2024-6-17 02:12

只能支持20M以下传输吗?一个apk文件都有100M了

黑白夜丶 发表于 2024-6-17 01:09

苹果手机可以用吗?最大可以传输多大的文件呢。

红蓝黄 发表于 2024-6-17 05:34

这类软件太多了吧

shojnhv 发表于 2024-6-17 06:46

谢谢,虽然这类软件很多,主要是学习相关编程技术来的

龍謹 发表于 2024-6-17 08:06

正在学习PY,偶来研究研究。

提拉米苏子冉 发表于 2024-6-17 08:09

跨网段局域网能传输吗

dhsfb 发表于 2024-6-17 08:37

偶也复制弄一个
页: [1] 2 3 4 5 6
查看完整版本: 局域网文件传输神器电脑、手机无缝对接!