nginx+fancyindex部署文件服务器(添加上传功能)

本文介绍了如何在使用Docker部署的Nginx环境中集成Flask文件上传功能,包括流式处理大文件、分片上传、合并文件以及使用Flask的after_request钩子记录日志。
摘要由CSDN通过智能技术生成

一、背景

使用docker部署的nginx+fancyindex部署文件服务器,缺少上传模块nginx-upload-module,没法直接添加模块重新编译。

二、准备文件上传服务

# -*- coding: utf-8 -*-
# author: zplin
# datetime: 2023/12/28 
# Description:文件上传服务

from flask import Flask, request, jsonify
from flask_cors import CORS
import os
from Log import Log

log = Log()

app = Flask(__name__)
CORS(app)
#日志记录,添加请求后的响应状态码response.status_code
@app.after_request
def after_request(response):
    log.info(
        f"{request.remote_addr} - - {request.method} {request.path} {request.environ['SERVER_PROTOCOL']} {response.status_code}")
    return response


# 上传文件接口
@app.route('/upload_file/<path:subpath>', methods=['POST'])
def upload_file(subpath):
    dirpath = r'E:\nginx\download\\'
    file_chunk = request.stream.read()  # 读取文件流的数据
    file_path = os.path.join(dirpath, subpath)  # 设置文件存储的路径
    os.makedirs(os.path.dirname(file_path), exist_ok=True)  # 确保目录存在

    chunk_number = request.args.get('chunk_number')  # 获取分片计数器参数
    if chunk_number.isdigit():
        chunk_number = int(chunk_number)  # 获取分片计数器参数
        if chunk_number == 0:
            if os.path.exists(file_path):
                # print(file_path)
                os.remove(file_path)  # 如果文件存在,则删除原文件

    with open(file_path, 'ab') as f:
        f.write(file_chunk)  # 将数据写入文件,使用追加模式

    # 检查是否是最后一个分片,如果是,进行文件合并
    if 'chunk_number' in request.args and request.args.get('chunk_number') == 'end':
        merge_file(dirpath, subpath)  # 调用合并函数

    return jsonify({'message': f'{os.path.basename(file_path)} chunk {str(chunk_number)} uploaded successfully'}), 200


# 合并文件
def merge_file(dirpath, subpath):
    file_list = [f for f in os.listdir(dirpath) if f.startswith(subpath) and os.path.isfile(os.path.join(dirpath, f))]
    file_list.sort(key=lambda x: int(x.split("_")[1]))  # 根据文件名中的分片序号进行排序

    file_path = os.path.join(dirpath, subpath)  # 设置文件合并后的路径
    with open(file_path, 'ab') as f:  # 将分片文件内容逐一合并
        for chunk_file in file_list:
            with open(os.path.join(dirpath, chunk_file), 'rb') as chunk:
                f.write(chunk.read())
            os.remove(os.path.join(dirpath, chunk_file))  # 删除已合并的分片文件

    log.info(f"File {file_path} merged successfully")


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

三、添加上传文件入口

修改Fancyindex容器下/theme/Nginx-Fancyindex-Theme/header.html,这里设置为点击3次Fancyindex打开Windows文件选择弹窗。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/721876146be448af9c0440ce2b77747a.png)
给Fancyindex添加文件输入框
        <div class="mdl-layout__drawer">
            <ul class="mdl-list">
                <li class="mdl-layout-title mdl-list__item mdl-navigation__link">
					<span class="mdl-color-text--primary mdl-list__item-primary-content" id="FancyIndex" onclick="handleFancyIndexClick()">
                        <i class="mdl-color-text--primary material-icons mdl-list__item-icon">filter_drama</i>
                        FancyIndex
                    </span>
                </li>
            </ul>
			<input type="file" id="fileInput" style="display: none;" onchange="handleFileSelect(event)" />

定义handleFileSelect方法

let clickCount = 0;

function handleFancyIndexClick() {
  clickCount++;
  if (clickCount === 3) {
    clickCount = 0; // 重置点击次数
    document.getElementById('fileInput').click(); // 触发文件选择框点击
  }
}
document.getElementById('FancyIndex').addEventListener('click', handleFancyIndexClick);

四、选择文件后上传文件到服务器

function handleFileSelect(event) {
  let file = event.target.files[0];  // 从事件对象中获取用户选择的文件
  if (file) {
  	//获取当前路径,将文件存到当前目录
    const subpath = document.getElementById('currentPathTitle').textContent.replace(/^\//, '');
    let chunkSize = 10 * 1024 * 1024; 
	let totalChunks = Math.ceil(file.size / chunkSize);  // 计算分片总数
    let chunkNumber = 0;  // 初始化分片计数器
    let start = 0;
    let end = 0;

    function readChunk() {
      let blob = file.slice(start, end);
      if (blob.size > 0) {
        let reader = new FileReader();
        reader.onload = function(e) {
          let data = e.target.result;
          let serverUrl = 'http://127.0.0.1:5000/upload_file/';  // 服务器接收文件的URL
          let url = serverUrl + subpath + file.name + '?chunk_number=' + (chunkNumber === totalChunks - 1 ? 'end' : chunkNumber.toString());
          fetch(url, {
            method: 'POST',
            body: data, // 直接传入 ArrayBuffer 数据
          }).then(response => response.text()).then(data => {
            console.log(data);  // 打印服务器的原始响应内容
			if (data.includes("uploaded successfully")) {
            chunkNumber++;
            start = end;
            end = start + chunkSize;
            readChunk();  // 继续下一个分片的读取和上传
          }else {
			console.log(url+"失败"); 
          } 
          });
        }
        reader.readAsArrayBuffer(blob);
      }
    }
    start = 0;
    end = chunkSize;
    readChunk();
  }
}

document.getElementById('FancyIndex').addEventListener('click', handleFancyIndexClick);
document.getElementById('fileInput').addEventListener('change', handleFileSelect);

五、header.html中引用上传文件JS(/fancyindex/uploadFile.js)

    <script src="/fancyindex/mdl/material.min.js"></script>
    <link rel="stylesheet" href="/fancyindex/mdl/material.min.css">
    <link rel="stylesheet" href="/fancyindex/fancyindex.css">
    <script src="/fancyindex/uploadFile.js"></script>
	
</head>

六、记录

1、使用flask.after_request钩子函数,在函数中获取响应状态码并写入日志。

@app.after_request
def after_request(response):
    log.info(
        f"{request.remote_addr} - - {request.method} {request.path} {request.environ['SERVER_PROTOCOL']} {response.status_code}")
    return response

2、大文件传输处理

  1. 使用流式传输: 避免将整个文件加载到内存中再进行上传。在Flask中用request.stream来处理流式传输。
  2. 分片上传: 考虑将大文件进行分片处理,分成若干小块分别上传,然后在服务器端进行合并。
	file_chunk = request.stream.read()  # 读取文件流的数据
	with open(file_path, 'ab') as f:
	    f.write(file_chunk)  # 将数据写入文件,使用追加模式

3、同名文件删除

分片上传使用追加模式写入,在上传时添加分片计数器chunk_number,当chunk_number为0而文件又存在时删除文件。

    chunk_number = request.args.get('chunk_number')  # 获取分片计数器参数
    if chunk_number.isdigit():
        chunk_number = int(chunk_number)  # 获取分片计数器参数
        if chunk_number == 0:
            if os.path.exists(file_path):
                # print(file_path)
                os.remove(file_path)  # 如果文件存在,则删除原文件

4、文件流按顺序整合

背景:文件上传后有问题,如:上传的zip解压出错
上传成功后再执行下一个分片的请求

if (data.includes("uploaded successfully")) {
            chunkNumber++;
            start = end;
            end = start + chunkSize;
            readChunk();  // 继续下一个分片的读取和上传
          }
  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值