用脚本实现文件快递柜功能

wen 实用脚本 2

本文目录导读:

用脚本实现文件快递柜功能

  1. 完整实现代码
  2. 安装与运行
  3. 主要功能
  4. 配置说明
  5. 使用示例

我来帮你实现一个文件快递柜功能的脚本,这个系统可以实现文件的临时存储、分享和下载功能。

完整实现代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
文件快递柜系统 - 基于Flask的临时文件分享服务
"""
import os
import hashlib
import uuid
import time
import shutil
from datetime import datetime, timedelta
from flask import Flask, request, render_template_string, redirect, url_for, send_file, flash, jsonify
from werkzeug.utils import secure_filename
# ==================== 配置 ====================
app = Flask(__name__)
app.secret_key = os.urandom(24)
# 文件存储配置
UPLOAD_FOLDER = 'file_locker'
EXPIRE_DAYS = 7  # 文件默认保留天数
MAX_CONTENT_LENGTH = 100 * 1024 * 1024  # 最大文件大小 (100MB)
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'doc', 'docx', 'xls', 'xlsx', 'zip', 'rar', '7z', 'mp4', 'mp3'}
# ==================== HTML模板 ====================
INDEX_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>📁 文件快递柜</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        .container {
            max-width: 800px;
            margin: 0 auto;
            background: white;
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .header h1 {
            color: #333;
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        .header p {
            color: #666;
            font-size: 1.1em;
        }
        .upload-area {
            border: 2px dashed #ccc;
            border-radius: 15px;
            padding: 40px;
            text-align: center;
            transition: all 0.3s;
            cursor: pointer;
        }
        .upload-area:hover {
            border-color: #667eea;
            background: #f8f9ff;
        }
        .upload-icon {
            font-size: 48px;
            color: #667eea;
        }
        .upload-btn {
            background: #667eea;
            color: white;
            border: none;
            padding: 12px 30px;
            border-radius: 25px;
            font-size: 16px;
            cursor: pointer;
            transition: all 0.3s;
            margin-top: 15px;
        }
        .upload-btn:hover {
            background: #5a67d8;
            transform: translateY(-2px);
        }
        .input-group {
            margin: 15px 0;
            text-align: left;
        }
        .input-group label {
            display: block;
            margin-bottom: 5px;
            color: #555;
            font-weight: 500;
        }
        .input-group input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 8px;
            font-size: 14px;
        }
        .file-list {
            margin-top: 30px;
        }
        .file-list h2 {
            color: #333;
            margin-bottom: 15px;
            border-bottom: 2px solid #f0f0f0;
            padding-bottom: 10px;
        }
        .file-item {
            background: #f8f9fa;
            border-radius: 10px;
            padding: 15px;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            transition: all 0.3s;
        }
        .file-item:hover {
            background: #eef0ff;
            transform: translateX(5px);
        }
        .file-info {
            flex: 1;
        }
        .file-name {
            font-weight: 600;
            color: #333;
        }
        .file-meta {
            color: #888;
            font-size: 0.8em;
            margin-top: 5px;
        }
        .file-actions {
            display: flex;
            gap: 10px;
        }
        .btn {
            padding: 8px 15px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            text-decoration: none;
            font-size: 13px;
            transition: all 0.3s;
        }
        .btn-download {
            background: #48bb78;
            color: white;
        }
        .btn-download:hover {
            background: #38a169;
        }
        .btn-delete {
            background: #f56565;
            color: white;
        }
        .btn-delete:hover {
            background: #e53e3e;
        }
        .btn-share {
            background: #4299e1;
            color: white;
        }
        .btn-share:hover {
            background: #3182ce;
        }
        .info-box {
            background: #ebf8ff;
            border-left: 4px solid #4299e1;
            padding: 15px;
            border-radius: 8px;
            margin: 20px 0;
        }
        .error-box {
            background: #fff5f5;
            border-left: 4px solid #fc8181;
            padding: 15px;
            border-radius: 8px;
            margin: 20px 0;
        }
        .flash-message {
            padding: 15px;
            border-radius: 8px;
            margin: 10px 0;
            animation: slideIn 0.3s ease;
        }
        @keyframes slideIn {
            from { transform: translateY(-20px); opacity: 0; }
            to { transform: translateY(0); opacity: 1; }
        }
        .flash-success { background: #c6f6d5; color: #22543d; }
        .flash-error { background: #fed7d7; color: #9b2c2c; }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📁 文件快递柜</h1>
            <p>安全、便捷的文件临时存储与分享服务</p>
        </div>
        {% with messages = get_flashed_messages(with_categories=true) %}
            {% if messages %}
                {% for category, message in messages %}
                    <div class="flash-message flash-{{ category }}">{{ message }}</div>
                {% endfor %}
            {% endif %}
        {% endwith %}
        <div class="upload-area" onclick="document.getElementById('fileInput').click()">
            <div class="upload-icon">📤</div>
            <p style="margin: 15px 0; color: #666;">点击上传文件或拖拽文件到此处</p>
            <form action="/upload" method="post" enctype="multipart/form-data" id="uploadForm">
                <input type="file" name="file" id="fileInput" style="display: none;" onchange="document.getElementById('uploadForm').submit()">
                <div class="input-group">
                    <label for="expire">过期时间(天):</label>
                    <input type="number" name="expire" id="expire" value="7" min="1" max="30">
                </div>
                <div class="input-group">
                    <label for="password">设置密码(可选):</label>
                    <input type="text" name="password" id="password" placeholder="留空则无需密码">
                </div>
                <button type="submit" class="upload-btn">📤 上传文件</button>
            </form>
        </div>
        <div class="file-list">
            <h2>📂 文件列表</h2>
            {% for file in files %}
            <div class="file-item">
                <div class="file-info">
                    <div class="file-name">{{ file.original_name }}</div>
                    <div class="file-meta">
                        大小: {{ file.size }} | 
                        过期: {{ file.expire_time }} | 
                        分享码: {{ file.share_code }}
                    </div>
                </div>
                <div class="file-actions">
                    <a href="/share/{{ file.share_code }}" class="btn btn-share">🔗 分享</a>
                    <a href="/download/{{ file.share_code }}" class="btn btn-download">⬇ 下载</a>
                    <a href="/delete/{{ file.share_code }}" class="btn btn-delete" onclick="return confirm('确定删除这个文件吗?')">🗑 删除</a>
                </div>
            </div>
            {% else %}
            <div class="info-box">
                📭 快递柜为空,上传文件后这里会显示
            </div>
            {% endfor %}
        </div>
    </div>
</body>
</html>
'''
SHARE_TEMPLATE = '''
<!DOCTYPE html>
<html>
<head>📁 文件分享 - {{ file.original_name }}</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }
        .container {
            max-width: 500px;
            width: 100%;
            background: white;
            border-radius: 20px;
            padding: 40px;
            box-shadow: 0 20px 60px rgba(0,0,0,0.3);
        }
        .header {
            text-align: center;
            margin-bottom: 30px;
        }
        .file-icon {
            font-size: 64px;
            margin-bottom: 15px;
        }
        .file-name {
            font-size: 1.3em;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }
        .file-info {
            color: #666;
            font-size: 0.9em;
            margin-bottom: 20px;
        }
        .btn-download {
            display: block;
            width: 100%;
            padding: 15px;
            background: #48bb78;
            color: white;
            border: none;
            border-radius: 10px;
            font-size: 18px;
            text-align: center;
            text-decoration: none;
            cursor: pointer;
            transition: all 0.3s;
            margin-bottom: 15px;
        }
        .btn-download:hover {
            background: #38a169;
            transform: translateY(-2px);
        }
        .password-form {
            margin-top: 20px;
        }
        .password-form input {
            width: 100%;
            padding: 12px;
            border: 1px solid #ddd;
            border-radius: 8px;
            font-size: 14px;
            margin-bottom: 10px;
        }
        .btn-submit {
            width: 100%;
            padding: 12px;
            background: #4299e1;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 16px;
        }
        .btn-back {
            display: block;
            text-align: center;
            color: #667eea;
            text-decoration: none;
            margin-top: 20px;
        }
        .error {
            color: #e53e3e;
            margin-bottom: 15px;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <div class="file-icon">📄</div>
            <div class="file-name">{{ file.original_name }}</div>
            <div class="file-info">
                大小: {{ file.size }} | 过期时间: {{ file.expire_time }}
            </div>
        </div>
        {% if error %}
        <div class="error">{{ error }}</div>
        {% endif %}
        {% if file.has_password %}
        <form action="/verify/{{ file.share_code }}" method="post" class="password-form">
            <input type="password" name="password" placeholder="请输入文件密码" required>
            <button type="submit" class="btn-submit">🔓 验证密码</button>
        </form>
        {% else %}
        <a href="/download/{{ file.share_code }}" class="btn-download">⬇ 下载文件</a>
        {% endif %}
        <a href="/" class="btn-back">← 返回首页</a>
    </div>
</body>
</html>
'''
# ==================== 文件系统管理 ====================
class FileLocker:
    """文件快递柜管理系统"""
    def __init__(self, base_path=UPLOAD_FOLDER):
        self.base_path = base_path
        self.metadata_file = os.path.join(base_path, 'metadata.json')
        self.metadata = {}
        self._ensure_directories()
        self._load_metadata()
    def _ensure_directories(self):
        """确保目录结构存在"""
        if not os.path.exists(self.base_path):
            os.makedirs(self.base_path)
    def _load_metadata(self):
        """加载文件元数据"""
        import json
        try:
            if os.path.exists(self.metadata_file):
                with open(self.metadata_file, 'r', encoding='utf-8') as f:
                    self.metadata = json.load(f)
        except Exception as e:
            print(f"加载元数据失败: {e}")
            self.metadata = {}
    def _save_metadata(self):
        """保存文件元数据"""
        import json
        try:
            with open(self.metadata_file, 'w', encoding='utf-8') as f:
                json.dump(self.metadata, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存元数据失败: {e}")
    def _generate_share_code(self):
        """生成唯一的分享码"""
        return uuid.uuid4().hex[:8]
    def _calculate_file_size(self, filepath):
        """计算文件大小(友好格式)"""
        size = os.path.getsize(filepath)
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024:
                return f"{size:.1f} {unit}"
            size /= 1024
        return f"{size:.1f} TB"
    def _check_expired_files(self):
        """检查并清理过期文件"""
        now = datetime.now()
        expired_codes = []
        for code, info in list(self.metadata.items()):
            expire_time = datetime.fromisoformat(info['expire_time'])
            if expire_time < now:
                expired_codes.append(code)
                # 删除物理文件
                file_path = os.path.join(self.base_path, info['stored_name'])
                if os.path.exists(file_path):
                    os.remove(file_path)
        # 删除过期元数据
        for code in expired_codes:
            del self.metadata[code]
        if expired_codes:
            self._save_metadata()
        return expired_codes
    def upload_file(self, file, expire_days=7, password=None):
        """上传文件到快递柜"""
        if not file or file.filename == '':
            return None, "请选择要上传的文件"
        # 检查文件扩展名
        filename = secure_filename(file.filename)
        if not filename:
            return None, "无效的文件名"
        # 生成存储信息
        share_code = self._generate_share_code()
        stored_name = f"{share_code}_{filename}"
        file_path = os.path.join(self.base_path, stored_name)
        # 计算过期时间
        expire_time = datetime.now() + timedelta(days=expire_days)
        # 保存文件
        try:
            file.save(file_path)
        except Exception as e:
            return None, f"文件保存失败: {str(e)}"
        # 保存元数据
        self.metadata[share_code] = {
            'original_name': filename,
            'stored_name': stored_name,
            'size': self._calculate_file_size(file_path),
            'upload_time': datetime.now().isoformat(),
            'expire_time': expire_time.isoformat(),
            'password': password,
            'has_password': bool(password),
            'download_count': 0
        }
        self._save_metadata()
        return share_code, f"文件上传成功!分享码: {share_code}"
    def get_file_info(self, share_code):
        """获取文件信息"""
        self._check_expired_files()
        return self.metadata.get(share_code)
    def verify_password(self, share_code, password):
        """验证文件密码"""
        info = self.metadata.get(share_code)
        if not info:
            return False
        return info.get('password') == password
    def download_file(self, share_code):
        """获取文件下载路径"""
        info = self.metadata.get(share_code)
        if not info:
            return None, "文件不存在或已过期"
        file_path = os.path.join(self.base_path, info['stored_name'])
        if not os.path.exists(file_path):
            del self.metadata[share_code]
            self._save_metadata()
            return None, "文件已被删除"
        # 更新下载计数
        info['download_count'] = info.get('download_count', 0) + 1
        self._save_metadata()
        return file_path, info['original_name']
    def delete_file(self, share_code):
        """删除文件"""
        info = self.metadata.get(share_code)
        if not info:
            return False, "文件不存在"
        file_path = os.path.join(self.base_path, info['stored_name'])
        if os.path.exists(file_path):
            os.remove(file_path)
        del self.metadata[share_code]
        self._save_metadata()
        return True, "文件已删除"
    def list_files(self):
        """列出所有有效文件"""
        self._check_expired_files()
        files = []
        for code, info in self.metadata.items():
            files.append({
                'share_code': code,
                **info
            })
        return sorted(files, key=lambda x: x['upload_time'], reverse=True)
# ==================== Flask 路由 ====================
locker = FileLocker()
@app.route('/')
def index():
    """首页"""
    files = locker.list_files()
    return render_template_string(INDEX_TEMPLATE, files=files)
@app.route('/upload', methods=['POST'])
def upload():
    """上传文件"""
    file = request.files.get('file')
    expire_days = int(request.form.get('expire', EXPIRE_DAYS))
    password = request.form.get('password', '').strip() or None
    share_code, message = locker.upload_file(file, expire_days, password)
    if share_code:
        flash(message, 'success')
    else:
        flash(message, 'error')
    return redirect(url_for('index'))
@app.route('/share/<share_code>')
def share(share_code):
    """文件分享页面"""
    info = locker.get_file_info(share_code)
    if not info:
        flash('文件不存在或已过期', 'error')
        return redirect(url_for('index'))
    error = request.args.get('error')
    return render_template_string(SHARE_TEMPLATE, file=info, error=error, share_code=share_code)
@app.route('/verify/<share_code>', methods=['POST'])
def verify(share_code):
    """验证密码"""
    password = request.form.get('password', '')
    if locker.verify_password(share_code, password):
        return redirect(url_for('download', share_code=share_code))
    else:
        return redirect(url_for('share', share_code=share_code, error='密码错误'))
@app.route('/download/<share_code>')
def download(share_code):
    """下载文件"""
    file_path, filename = locker.download_file(share_code)
    if file_path:
        return send_file(
            file_path,
            as_attachment=True,
            download_name=filename
        )
    else:
        flash(filename, 'error')
        return redirect(url_for('index'))
@app.route('/delete/<share_code>')
def delete(share_code):
    """删除文件"""
    success, message = locker.delete_file(share_code)
    if success:
        flash(message, 'success')
    else:
        flash(message, 'error')
    return redirect(url_for('index'))
# ==================== 主程序入口 ====================
if __name__ == '__main__':
    print("""
    ╔═══════════════════════════════════════════╗
    ║         📁 文件快递柜系统 v1.0           ║
    ╠═══════════════════════════════════════════╣
    ║  启动成功!                               ║
    ║  访问地址: http://localhost:5000          ║
    ║                                           ║
    ║  功能说明:                               ║
    ║  • 文件上传与分享                        ║
    ║  • 密码保护                              ║
    ║  • 自动过期清理                          ║
    ║  • 下载统计                              ║
    ╚═══════════════════════════════════════════╝
    """)
    # 清理过期文件
    expired = locker._check_expired_files()
    if expired:
        print(f"已清理 {len(expired)} 个过期文件")
    app.run(debug=True, host='0.0.0.0', port=5000)

安装与运行

安装依赖

pip install flask werkzeug

运行服务

python file_locker.py

访问服务

打开浏览器访问:http://localhost:5000

主要功能

📤 文件上传

  • 支持多种文件格式
  • 可设置过期时间(1-30天)
  • 可选密码保护

🔗 文件分享

  • 生成唯一分享码
  • 分享页面展示文件信息
  • 密码验证访问

⬇ 文件下载

  • 直接下载或密码验证后下载
  • 自动记录下载次数
  • 原始文件名下载

🗑 文件管理

  • 自动清理过期文件
  • 手动删除文件
  • 文件列表展示

配置说明

在脚本顶部的配置区域可以修改:

  • UPLOAD_FOLDER: 文件存储目录
  • EXPIRE_DAYS: 默认过期天数
  • MAX_CONTENT_LENGTH: 最大文件大小
  • ALLOWED_EXTENSIONS: 允许的文件类型

使用示例

  1. 上传文件

    • 点击上传区域选择文件
    • 设置过期时间
    • 可选设置访问密码
    • 点击上传
  2. 分享文件

    • 复制分享码或分享链接
    • 发送给需要的人
  3. 下载文件

    • 访问分享链接
    • 输入密码(如果设置了)
    • 点击下载

这个文件快递柜系统就像现实中的快递柜一样,方便临时存放和分享文件,自动管理文件生命周期!

抱歉,评论功能暂时关闭!