python(42) : 监听本地文件夹上传到服务器指定目录

1.前言

        服务器部署http服务, 本地代码定时扫描或收到点击弹窗按钮扫描文件内容, 初次扫描记录时间到本地, 再次扫描查询更新时间大于记录时间的文件或者记录中不存在的文件夹, 通过http服务上传文件到服务指定目录或者创建文件夹, 扫描支持配置忽略条件。

        配置信息示例:

# 监听的文件夹和远程服务器路径
config = {
    r"F:\test\api": r"/home/test/api",
    r"F:\test\worker": r"/home/test/worker",
}
# 远程服务器地址
remote_host = f"http://192.168.1.2"  

# 是否是调试模式, 调试模式下不进行上传
is_debug = False 

2.服务器http服务

2.1.listen_server.py

# -*- coding: utf-8 -*-
"""
Flask HTTP接口服务器
提供文件上传和目录创建功能
"""

import os
import argparse
from flask import Flask, request, jsonify
from werkzeug.utils import secure_filename
import logging
from datetime import datetime
from flask_cors import CORS
import traceback

# pip install flask flask-cors  -i https://mirrors.aliyun.com/pypi/simple/ requests

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


app = Flask(__name__)
CORS(app)  # 支持跨域


# 设置应用编码为UTF-8
app.config['JSON_AS_ASCII'] = False  # 允许JSON响应包含非ASCII字符
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True

# 允许所有文件类型上传
ALLOW_ALL_FILES = True

def ensure_directory_exists(directory_path):
    """确保目录存在,如果不存在则创建"""
    try:
        os.makedirs(directory_path, exist_ok=True)
        logger.info(f"✅ 目录创建成功: {directory_path}")
        return True
    except Exception as e:
        logger.error(f"❌ 目录创建失败: {directory_path}, 错误: {str(e)}")
        return False

@app.route('/health', methods=['GET'])
def health_check():
    """健康检查接口"""
    return jsonify({
        'status': 'healthy',
        'timestamp': datetime.now().isoformat(),
        'message': 'HTTP文件上传服务器运行正常'
    })

@app.route('/file_sync/create_folder', methods=['POST'])
def create_folder():
    """创建文件夹接口"""
    try:
        data = request.get_json()
        if not data or 'folder_path' not in data:
            return jsonify({
                'success': False,
                'error': '缺少folder_path参数'
            }), 400
        
        folder_path = data['folder_path']
        
        # 构建完整的服务器路径
        full_path = os.path.join(UPLOAD_FOLDER, folder_path.lstrip('/'))
        
        # 确保路径安全
        full_path = os.path.normpath(full_path)
        if not full_path.startswith(UPLOAD_FOLDER):
            return jsonify({
                'success': False,
                'error': '路径不安全,不允许访问基础目录之外的文件'
            }), 400
        
        # 创建目录
        if ensure_directory_exists(full_path):
            return jsonify({
                'success': True,
                'message': f'文件夹创建成功: {folder_path}',
                'full_path': full_path
            })
        else:
            return jsonify({
                'success': False,
                'error': f'文件夹创建失败: {folder_path}'
            }), 500
            
    except Exception as e:
        logger.error(f"创建文件夹异常: {str(e)}")
        return jsonify({
            'success': False,
            'error': f'服务器内部错误: {str(e)}'
        }), 500

@app.route('/file_sync/upload_file', methods=['POST'])
def upload_file():
    """文件上传接口 - 支持完整路径"""
    try:
        # 检查是否有文件
        if 'file' not in request.files:
            return jsonify({
                'success': False,
                'error': '没有找到文件'
            }), 400
        
        file = request.files['file']
        if file.filename == '':
            return jsonify({
                'success': False,
                'error': '没有选择文件'
            }), 400
        
        # 获取目标路径 - 支持完整路径或相对路径
        target_path = request.form.get('target_path', '')
        if not target_path:
            return jsonify({
                'success': False,
                'error': '缺少target_path参数'
            }), 400
        
        # 判断是否为完整路径
        logger.info(f"target_path: {target_path}")
        if target_path.startswith('/'):
            # 完整路径,直接使用
            logger.info(f"完整路径,直接使用")
            full_path = target_path
        else:
            # 相对路径,拼接到基础目录
            full_path = os.path.join(UPLOAD_FOLDER, target_path.lstrip('/'))
            logger.info(f"相对路径,拼接到基础目录,基础目录: {UPLOAD_FOLDER}")
        
        # 确保路径安全
        full_path = os.path.normpath(full_path)
        if not full_path.startswith(UPLOAD_FOLDER):
            logger.info(f"路径不安全,不允许访问基础目录之外的文件,基础目录: {UPLOAD_FOLDER}")
            return jsonify({
                'success': False,
                'error': '路径不安全,不允许访问基础目录之外的文件'
            }), 400
        
        # 确保目标目录存在
        target_dir = os.path.dirname(full_path)
        if not ensure_directory_exists(target_dir):
            logger.info(f"目标目录创建失败: {target_dir}")
            return jsonify({
                'success': False,
                'error': f'目标目录创建失败: {target_dir}'
            }), 500
        
        # 保存文件
        filename = secure_filename(file.filename)
        if not filename:
            filename = os.path.basename(full_path)
        
        # 判断full_path是文件路径还是目录路径
        # 如果full_path以斜杠结尾,说明是目录路径
        # 如果full_path没有扩展名但也不是目录(通过检查是否以斜杠结尾),则认为是文件路径
        if full_path.endswith('/') or full_path.endswith('\\'):
            # full_path是目录路径,需要拼接文件名
            save_path = os.path.join(full_path, filename)
        else:
            # full_path是文件路径,直接使用
            save_path = full_path
        
        file.save(save_path)
        
        logger.info(f"✅ 文件上传成功: {save_path}")
        return jsonify({
            'success': True,
            'message': f'文件上传成功: {target_path}',
            'full_path': save_path,
            'file_size': os.path.getsize(save_path)
        })
        
    except Exception as e:
        logger.error(f"文件上传异常: {traceback.format_exc()}")
        return jsonify({
            'success': False,
            'error': f'服务器内部错误: {str(e)}'
        }), 500

@app.route('/file_sync/batch_upload', methods=['POST'])
def batch_upload():
    """批量上传接口"""
    try:
        data = request.get_json()
        if not data or 'files' not in data:
            return jsonify({
                'success': False,
                'error': '缺少files参数'
            }), 400
        
        results = []
        for file_info in data['files']:
            if 'file_path' not in file_info or 'target_path' not in file_info:
                results.append({
                    'file_path': file_info.get('file_path', 'unknown'),
                    'success': False,
                    'error': '缺少必要参数'
                })
                continue
            
            # 这里需要客户端提供文件内容,实际实现可能需要调整
            # 暂时返回模拟结果
            results.append({
                'file_path': file_info['file_path'],
                'target_path': file_info['target_path'],
                'success': True,
                'message': '批量上传功能待实现'
            })
        
        return jsonify({
            'success': True,
            'message': '批量上传完成',
            'results': results
        })
        
    except Exception as e:
        logger.error(f"批量上传异常: {str(e)}")
        return jsonify({
            'success': False,
            'error': f'服务器内部错误: {str(e)}'
        }), 500

@app.errorhandler(404)
def not_found(error):
    return jsonify({
        'success': False,
        'error': '接口不存在'
    }), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({
        'success': False,
        'error': '服务器内部错误'
    }), 500

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='图片超清HTTP文件上传服务器')
    parser.add_argument('--port', '-p', type=int, default=5100,
                       help='服务器端口,默认5000')
    parser.add_argument('--host', default='0.0.0.0',
                       help='服务器主机地址,默认0.0.0.0')
    parser.add_argument('--upload-folder', '-u', default='/home/test',
                       help='上传目录,默认/home/test')
    parser.add_argument('--debug', '-d', action='store_true',
                       help='启用调试模式')

    args = parser.parse_args()

    # 设置全局配置
    global UPLOAD_FOLDER
    UPLOAD_FOLDER = args.upload_folder

    # 确保上传目录存在
    ensure_directory_exists(UPLOAD_FOLDER)

    logger.info(f"🚀 图片超清HTTP文件上传服务器启动")
    logger.info(f"📁 上传目录: {UPLOAD_FOLDER}")
    logger.info(f"🌐 服务地址: http://{args.host}:{args.port}")

    app.run(host=args.host, port=args.port, debug=args.debug)

if __name__ == '__main__':
    main()

2.2.启动脚本

启动脚本不能传递参数! 这个参数会传递到python里面解析会报错

#!/bin/bash

# 配置参数
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SERVER_SCRIPT="$SCRIPT_DIR/listen_server.py"
PID_FILE="$SCRIPT_DIR/server.pid"
LOG_FILE="$SCRIPT_DIR/logs/server.log"
PORT=${1:-5000}  # 默认端口5000,可通过参数指定

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

# 检查Python环境
check_python() {
    if ! command -v python3 &> /dev/null; then
        log_error "Python3 未安装或不在PATH中"
        exit 1
    fi
    
    # 检查依赖
    if ! python3 -c "import flask, requests" 2>/dev/null; then
        log_warning "缺少依赖包,正在安装..."
        pip3 i
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值