本文目录导读:

我来帮你实现一个文件快递柜功能的脚本,这个系统可以实现文件的临时存储、分享和下载功能。
完整实现代码
#!/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: 允许的文件类型
使用示例
-
上传文件
- 点击上传区域选择文件
- 设置过期时间
- 可选设置访问密码
- 点击上传
-
分享文件
- 复制分享码或分享链接
- 发送给需要的人
-
下载文件
- 访问分享链接
- 输入密码(如果设置了)
- 点击下载
这个文件快递柜系统就像现实中的快递柜一样,方便临时存放和分享文件,自动管理文件生命周期!