remove redis
This commit is contained in:
parent
4985344911
commit
989b9484a7
24
README.md
24
README.md
@ -41,19 +41,12 @@ cp .env.example .env
|
||||
|
||||
# 基础部署
|
||||
docker-compose up -d
|
||||
|
||||
# 包含Traefik反向代理 (生产环境推荐)
|
||||
docker-compose --profile traefik up -d
|
||||
|
||||
# 包含Redis缓存
|
||||
docker-compose --profile redis up -d
|
||||
```
|
||||
|
||||
**访问服务:**
|
||||
- 🌐 Web界面: http://localhost:8000
|
||||
- 📖 API文档: http://localhost:8000/docs
|
||||
- ℹ️ API信息: http://localhost:8000/api
|
||||
- 如果启用了Traefik: http://traefik.localhost:8080
|
||||
|
||||
### 方式3: 本地运行
|
||||
|
||||
@ -203,20 +196,11 @@ python cli.py info ABCD1234
|
||||
|
||||
### 生产环境
|
||||
|
||||
1. 使用Traefik反向代理:
|
||||
```bash
|
||||
docker-compose --profile traefik up -d
|
||||
```
|
||||
|
||||
2. 配置SSL证书:
|
||||
- 修改`.env`中的`ACME_EMAIL`
|
||||
- 确保域名正确指向服务器
|
||||
|
||||
3. 数据持久化:
|
||||
1. 数据持久化:
|
||||
- 上传文件会保存在`./data/uploads`
|
||||
- 可以备份该目录
|
||||
|
||||
4. 监控和日志:
|
||||
2. 监控和日志:
|
||||
- 日志输出到`./data/logs`
|
||||
- 支持健康检查
|
||||
|
||||
@ -224,9 +208,7 @@ docker-compose --profile traefik up -d
|
||||
|
||||
1. 设置合适的文件大小限制
|
||||
2. 定期清理过期文件
|
||||
3. 使用HTTPS(生产环境)
|
||||
4. 限制访问IP(如需要)
|
||||
5. 设置反向代理限流
|
||||
3. 限制访问IP(如需要)
|
||||
|
||||
## 开发
|
||||
|
||||
|
||||
365
cli.py
365
cli.py
@ -1,365 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
文件传输服务 - 命令行工具
|
||||
支持文件上传、文本分享和下载功能
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
import httpx
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Prompt
|
||||
|
||||
console = Console()
|
||||
|
||||
# 默认服务器配置
|
||||
DEFAULT_SERVER = "http://localhost:8000"
|
||||
SERVER_URL = os.getenv("FILESHARE_SERVER", DEFAULT_SERVER)
|
||||
|
||||
class FileShareClient:
|
||||
def __init__(self, server_url: str = SERVER_URL):
|
||||
self.server_url = server_url.rstrip("/")
|
||||
self.client = httpx.Client(timeout=60.0)
|
||||
|
||||
def upload_file(self, file_path: Path) -> dict:
|
||||
"""上传文件"""
|
||||
if not file_path.exists():
|
||||
raise FileNotFoundError(f"文件不存在: {file_path}")
|
||||
|
||||
with open(file_path, "rb") as f:
|
||||
files = {"file": (file_path.name, f, "application/octet-stream")}
|
||||
response = self.client.post(f"{self.server_url}/api/upload", files=files)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"上传失败: {response.status_code} - {response.text}")
|
||||
|
||||
def share_text(self, content: str, filename: str = "shared_text.txt") -> dict:
|
||||
"""分享文本"""
|
||||
data = {"content": content, "filename": filename}
|
||||
response = self.client.post(f"{self.server_url}/api/share-text", json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"分享失败: {response.status_code} - {response.text}")
|
||||
|
||||
def download_file(self, code: str, output_dir: Path = Path(".")) -> Path:
|
||||
"""下载文件"""
|
||||
# 先获取文件信息
|
||||
info_response = self.client.get(f"{self.server_url}/api/info/{code}")
|
||||
if info_response.status_code != 200:
|
||||
raise Exception(f"获取文件信息失败: {info_response.status_code}")
|
||||
|
||||
file_info = info_response.json()
|
||||
filename = file_info["filename"]
|
||||
|
||||
# 下载文件
|
||||
response = self.client.get(f"{self.server_url}/api/download/{code}")
|
||||
if response.status_code != 200:
|
||||
raise Exception(f"下载失败: {response.status_code} - {response.text}")
|
||||
|
||||
# 保存文件
|
||||
output_path = output_dir / filename
|
||||
|
||||
# 如果文件存在,添加序号
|
||||
counter = 1
|
||||
original_path = output_path
|
||||
while output_path.exists():
|
||||
stem = original_path.stem
|
||||
suffix = original_path.suffix
|
||||
output_path = output_dir / f"{stem}_{counter}{suffix}"
|
||||
counter += 1
|
||||
|
||||
with open(output_path, "wb") as f:
|
||||
f.write(response.content)
|
||||
|
||||
return output_path
|
||||
|
||||
def get_info(self, code: str) -> dict:
|
||||
"""获取分享信息"""
|
||||
response = self.client.get(f"{self.server_url}/api/info/{code}")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"获取信息失败: {response.status_code} - {response.text}")
|
||||
|
||||
def list_shares(self) -> dict:
|
||||
"""列出所有分享"""
|
||||
response = self.client.get(f"{self.server_url}/api/shares")
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise Exception(f"获取列表失败: {response.status_code} - {response.text}")
|
||||
|
||||
@click.group()
|
||||
@click.option("--server", default=SERVER_URL, help="服务器地址")
|
||||
@click.pass_context
|
||||
def cli(ctx, server):
|
||||
"""文件传输服务命令行工具"""
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['client'] = FileShareClient(server)
|
||||
ctx.obj['server'] = server
|
||||
|
||||
@cli.command()
|
||||
@click.argument("file_path", type=click.Path(exists=True, path_type=Path))
|
||||
@click.pass_context
|
||||
def upload(ctx, file_path: Path):
|
||||
"""上传文件并获取分享码"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
try:
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task(f"上传文件 {file_path.name}...", total=None)
|
||||
result = client.upload_file(file_path)
|
||||
|
||||
# 显示结果
|
||||
panel = Panel.fit(
|
||||
f"[green]✓ 文件上传成功![/green]\n\n"
|
||||
f"[bold]分享码:[/bold] [yellow]{result['code']}[/yellow]\n"
|
||||
f"[bold]过期时间:[/bold] {result['expires_at']}\n"
|
||||
f"[bold]下载链接:[/bold] {ctx.obj['server']}{result['download_url']}",
|
||||
title="上传成功",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
console.print(f"\n[dim]使用命令下载: [bold]python cli.py download {result['code']}[/bold][/dim]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 上传失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@cli.command()
|
||||
@click.option("--text", "-t", help="要分享的文本内容")
|
||||
@click.option("--file", "-f", "text_file", type=click.Path(exists=True, path_type=Path), help="要分享的文本文件")
|
||||
@click.option("--filename", default="shared_text.txt", help="分享文件名")
|
||||
@click.pass_context
|
||||
def share_text(ctx, text: Optional[str], text_file: Optional[Path], filename: str):
|
||||
"""分享文本内容"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
# 确定文本内容
|
||||
if text_file:
|
||||
try:
|
||||
with open(text_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not filename.endswith(".txt") and text_file.suffix:
|
||||
filename = f"shared_{text_file.name}"
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 读取文件失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
elif text:
|
||||
content = text
|
||||
else:
|
||||
# 交互式输入
|
||||
console.print("[blue]请输入要分享的文本内容(按Ctrl+D或Ctrl+Z结束):[/blue]")
|
||||
lines = []
|
||||
try:
|
||||
while True:
|
||||
line = input()
|
||||
lines.append(line)
|
||||
except EOFError:
|
||||
content = "\n".join(lines)
|
||||
|
||||
if not content.strip():
|
||||
console.print("[red]❌ 文本内容不能为空[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task("分享文本...", total=None)
|
||||
result = client.share_text(content, filename)
|
||||
|
||||
# 显示结果
|
||||
panel = Panel.fit(
|
||||
f"[green]✓ 文本分享成功![/green]\n\n"
|
||||
f"[bold]分享码:[/bold] [yellow]{result['code']}[/yellow]\n"
|
||||
f"[bold]文件名:[/bold] {filename}\n"
|
||||
f"[bold]过期时间:[/bold] {result['expires_at']}\n"
|
||||
f"[bold]下载链接:[/bold] {ctx.obj['server']}{result['download_url']}",
|
||||
title="分享成功",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
console.print(f"\n[dim]使用命令下载: [bold]python cli.py download {result['code']}[/bold][/dim]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 分享失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@cli.command()
|
||||
@click.argument("code")
|
||||
@click.option("--output", "-o", type=click.Path(path_type=Path), default=".", help="输出目录")
|
||||
@click.pass_context
|
||||
def download(ctx, code: str, output: Path):
|
||||
"""通过分享码下载文件"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
try:
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
console=console,
|
||||
) as progress:
|
||||
task = progress.add_task(f"下载分享码 {code}...", total=None)
|
||||
output_path = client.download_file(code, output)
|
||||
|
||||
# 显示结果
|
||||
file_size = output_path.stat().st_size
|
||||
panel = Panel.fit(
|
||||
f"[green]✓ 文件下载成功![/green]\n\n"
|
||||
f"[bold]文件路径:[/bold] {output_path.absolute()}\n"
|
||||
f"[bold]文件大小:[/bold] {file_size:,} 字节",
|
||||
title="下载成功",
|
||||
border_style="green"
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 下载失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@cli.command()
|
||||
@click.argument("code")
|
||||
@click.pass_context
|
||||
def info(ctx, code: str):
|
||||
"""获取分享信息"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
try:
|
||||
result = client.get_info(code)
|
||||
|
||||
# 计算文件大小显示
|
||||
size = result['size']
|
||||
if size < 1024:
|
||||
size_str = f"{size} B"
|
||||
elif size < 1024 * 1024:
|
||||
size_str = f"{size / 1024:.1f} KB"
|
||||
else:
|
||||
size_str = f"{size / (1024 * 1024):.1f} MB"
|
||||
|
||||
# 显示信息
|
||||
status = "[red]已过期[/red]" if result['is_expired'] else "[green]有效[/green]"
|
||||
|
||||
panel = Panel.fit(
|
||||
f"[bold]分享码:[/bold] [yellow]{result['code']}[/yellow]\n"
|
||||
f"[bold]文件名:[/bold] {result['filename']}\n"
|
||||
f"[bold]文件类型:[/bold] {result['file_type']}\n"
|
||||
f"[bold]文件大小:[/bold] {size_str}\n"
|
||||
f"[bold]创建时间:[/bold] {result['created_at']}\n"
|
||||
f"[bold]过期时间:[/bold] {result['expires_at']}\n"
|
||||
f"[bold]状态:[/bold] {status}",
|
||||
title="分享信息",
|
||||
border_style="blue"
|
||||
)
|
||||
console.print(panel)
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 获取信息失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def list(ctx):
|
||||
"""列出所有分享"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
try:
|
||||
result = client.list_shares()
|
||||
|
||||
if result['total'] == 0:
|
||||
console.print("[yellow]没有找到任何分享[/yellow]")
|
||||
return
|
||||
|
||||
# 创建表格
|
||||
table = Table(title=f"所有分享 (共 {result['total']} 个)")
|
||||
table.add_column("分享码", style="yellow", no_wrap=True)
|
||||
table.add_column("文件名", style="blue")
|
||||
table.add_column("类型", style="green")
|
||||
table.add_column("大小", justify="right")
|
||||
table.add_column("剩余时间", justify="center")
|
||||
table.add_column("状态", justify="center")
|
||||
|
||||
for share in result['shares']:
|
||||
# 计算文件大小显示
|
||||
size = share['size']
|
||||
if size < 1024:
|
||||
size_str = f"{size}B"
|
||||
elif size < 1024 * 1024:
|
||||
size_str = f"{size / 1024:.1f}K"
|
||||
else:
|
||||
size_str = f"{size / (1024 * 1024):.1f}M"
|
||||
|
||||
# 剩余时间
|
||||
remaining = share.get('remaining_minutes', 0)
|
||||
if remaining > 0:
|
||||
remaining_str = f"{remaining}分钟"
|
||||
else:
|
||||
remaining_str = "已过期"
|
||||
|
||||
# 状态
|
||||
status = "[red]过期[/red]" if share['is_expired'] else "[green]有效[/green]"
|
||||
|
||||
table.add_row(
|
||||
share['code'],
|
||||
share['filename'][:30] + ("..." if len(share['filename']) > 30 else ""),
|
||||
share['file_type'].split('/')[-1] if '/' in share['file_type'] else share['file_type'],
|
||||
size_str,
|
||||
remaining_str,
|
||||
status
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
console.print(f"\n[dim]使用 'python cli.py info <分享码>' 查看详细信息[/dim]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 获取列表失败: {e}[/red]")
|
||||
sys.exit(1)
|
||||
|
||||
@cli.command()
|
||||
@click.pass_context
|
||||
def server_info(ctx):
|
||||
"""获取服务器信息"""
|
||||
client = ctx.obj['client']
|
||||
|
||||
try:
|
||||
response = client.client.get(f"{client.server_url}/")
|
||||
if response.status_code == 200:
|
||||
info = response.json()
|
||||
|
||||
panel = Panel.fit(
|
||||
f"[bold]服务名称:[/bold] {info['service']}\n"
|
||||
f"[bold]版本:[/bold] {info['version']}\n"
|
||||
f"[bold]服务器地址:[/bold] {client.server_url}\n\n"
|
||||
f"[bold]功能特性:[/bold]\n" +
|
||||
"\n".join([f" • {feature}" for feature in info['features']]),
|
||||
title="服务器信息",
|
||||
border_style="blue"
|
||||
)
|
||||
console.print(panel)
|
||||
else:
|
||||
console.print(f"[red]❌ 无法连接到服务器: {response.status_code}[/red]")
|
||||
|
||||
except Exception as e:
|
||||
console.print(f"[red]❌ 连接服务器失败: {e}[/red]")
|
||||
console.print(f"[dim]请确保服务器正在运行: {client.server_url}[/dim]")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
@ -1,71 +0,0 @@
|
||||
#!/bin/bash
|
||||
# curl命令示例脚本 - 展示文件传输服务的基本用法
|
||||
|
||||
SERVER_URL="http://localhost:8000"
|
||||
|
||||
echo "📡 文件传输服务 - curl命令示例"
|
||||
echo "================================="
|
||||
echo
|
||||
|
||||
# 检查服务器是否运行
|
||||
echo "🔍 检查服务器状态..."
|
||||
if curl -s "$SERVER_URL/api" > /dev/null; then
|
||||
echo "✅ 服务器运行正常"
|
||||
else
|
||||
echo "❌ 无法连接到服务器"
|
||||
echo "💡 请先启动服务: python app.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🌐 Web界面: $SERVER_URL"
|
||||
echo "📖 curl教程: $SERVER_URL/curl"
|
||||
echo
|
||||
|
||||
# 显示使用示例
|
||||
echo "📝 使用示例:"
|
||||
echo
|
||||
|
||||
echo "1. 📤 上传文件:"
|
||||
echo " curl -X POST -F \"file=@文件路径\" $SERVER_URL/api/upload"
|
||||
echo
|
||||
echo " 示例:"
|
||||
echo " curl -X POST -F \"file=@photo.jpg\" $SERVER_URL/api/upload"
|
||||
echo
|
||||
|
||||
echo "2. 📝 分享文本(超级简单):"
|
||||
echo " curl -X POST --data \"你的文本\" $SERVER_URL/api/text"
|
||||
echo
|
||||
echo " 示例:"
|
||||
echo " curl -X POST --data \"Hello World!\" $SERVER_URL/api/text"
|
||||
echo
|
||||
echo " 🔧 指定文件名:"
|
||||
echo " curl -X POST -F \"content=Hello World!\" -F \"filename=hello.txt\" $SERVER_URL/api/share-text-form"
|
||||
echo
|
||||
|
||||
echo "3. ⬇️ 下载文件:"
|
||||
echo " curl -O -J $SERVER_URL/api/download/分享码"
|
||||
echo
|
||||
echo " 示例:"
|
||||
echo " curl -O -J $SERVER_URL/api/download/AB12CD34"
|
||||
echo
|
||||
|
||||
echo "4. ℹ️ 查看文件信息:"
|
||||
echo " curl $SERVER_URL/api/info/分享码"
|
||||
echo
|
||||
echo " 示例:"
|
||||
echo " curl $SERVER_URL/api/info/AB12CD34"
|
||||
echo
|
||||
|
||||
echo "5. 📊 列出所有分享:"
|
||||
echo " curl $SERVER_URL/api/shares"
|
||||
echo
|
||||
|
||||
echo "================================="
|
||||
echo "💡 提示:"
|
||||
echo " - 无需安装任何额外工具"
|
||||
echo " - 分享码为8位大写字母+数字"
|
||||
echo " - 文件15分钟后自动过期"
|
||||
echo " - 最大支持100MB文件"
|
||||
echo
|
||||
echo "📖 查看完整教程: $SERVER_URL/curl"
|
||||
102
demo.sh
102
demo.sh
@ -1,102 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 文件传输服务演示脚本
|
||||
|
||||
SERVER_URL="http://localhost:8000"
|
||||
|
||||
echo "🎉 文件传输服务演示"
|
||||
echo "===================="
|
||||
echo
|
||||
|
||||
# 检查服务器
|
||||
echo "🔍 检查服务器状态..."
|
||||
if ! curl -s "$SERVER_URL/api" > /dev/null; then
|
||||
echo "❌ 服务器未启动,请先运行: python app.py"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 服务器运行正常"
|
||||
echo
|
||||
|
||||
# 演示1: 分享文本(超级简单方式)
|
||||
echo "📝 演示1: 分享文本(超级简单)"
|
||||
echo "命令: curl -X POST --data \"Hello from demo!\" $SERVER_URL/api/text"
|
||||
echo
|
||||
result1=$(curl -s -X POST --data "Hello from demo!" "$SERVER_URL/api/text")
|
||||
if echo "$result1" | grep -q '"code"'; then
|
||||
code1=$(echo "$result1" | grep -o '"code":"[^"]*"' | cut -d'"' -f4)
|
||||
echo "✅ 分享成功! 分享码: $code1"
|
||||
echo "🔗 下载链接: $SERVER_URL/api/download/$code1"
|
||||
else
|
||||
echo "❌ 分享失败"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 演示2: 创建临时文件并上传
|
||||
echo "📤 演示2: 上传文件"
|
||||
temp_file=$(mktemp)
|
||||
echo "这是一个演示文件
|
||||
创建时间: $(date)
|
||||
文件内容测试" > "$temp_file"
|
||||
|
||||
echo "命令: curl -X POST -F \"file=@$temp_file\" $SERVER_URL/api/upload"
|
||||
echo
|
||||
result2=$(curl -s -X POST -F "file=@$temp_file" "$SERVER_URL/api/upload")
|
||||
if echo "$result2" | grep -q '"code"'; then
|
||||
code2=$(echo "$result2" | grep -o '"code":"[^"]*"' | cut -d'"' -f4)
|
||||
echo "✅ 上传成功! 分享码: $code2"
|
||||
echo "🔗 下载链接: $SERVER_URL/api/download/$code2"
|
||||
else
|
||||
echo "❌ 上传失败"
|
||||
fi
|
||||
rm -f "$temp_file"
|
||||
echo
|
||||
|
||||
# 演示3: 表单方式分享文本
|
||||
echo "📋 演示3: 表单方式分享文本(可指定文件名)"
|
||||
echo "命令: curl -X POST -F \"content=#!/bin/bash
|
||||
echo 'Hello Shell!'\" -F \"filename=demo.sh\" $SERVER_URL/api/share-text-form"
|
||||
echo
|
||||
result3=$(curl -s -X POST -F "content=#!/bin/bash
|
||||
echo 'Hello Shell!'" -F "filename=demo.sh" "$SERVER_URL/api/share-text-form")
|
||||
if echo "$result3" | grep -q '"code"'; then
|
||||
code3=$(echo "$result3" | grep -o '"code":"[^"]*"' | cut -d'"' -f4)
|
||||
echo "✅ 分享成功! 分享码: $code3"
|
||||
echo "🔗 下载链接: $SERVER_URL/api/download/$code3"
|
||||
else
|
||||
echo "❌ 分享失败"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 演示4: 查看所有分享
|
||||
echo "📊 演示4: 查看所有分享"
|
||||
echo "命令: curl $SERVER_URL/api/shares"
|
||||
echo
|
||||
shares_result=$(curl -s "$SERVER_URL/api/shares")
|
||||
if echo "$shares_result" | grep -q '"total"'; then
|
||||
total=$(echo "$shares_result" | grep -o '"total":[0-9]*' | cut -d':' -f2)
|
||||
echo "✅ 当前共有 $total 个分享"
|
||||
echo
|
||||
echo "$shares_result" | python3 -m json.tool 2>/dev/null || echo "$shares_result"
|
||||
else
|
||||
echo "❌ 获取分享列表失败"
|
||||
fi
|
||||
echo
|
||||
|
||||
# 演示便捷函数
|
||||
echo "🚀 演示5: 便捷函数使用"
|
||||
echo "加载函数: source fileshare_functions.sh"
|
||||
echo "然后就可以使用超级简单的命令:"
|
||||
echo
|
||||
echo " upload photo.jpg # 上传文件"
|
||||
echo " share_text \"Hello World!\" # 分享文本"
|
||||
echo " download AB12CD34 # 下载文件"
|
||||
echo " info AB12CD34 # 查看信息"
|
||||
echo " list_shares # 列出分享"
|
||||
echo
|
||||
|
||||
echo "===================="
|
||||
echo "🎉 演示完成!"
|
||||
echo
|
||||
echo "📖 查看完整教程: $SERVER_URL/curl"
|
||||
echo "🌐 Web界面: $SERVER_URL"
|
||||
echo "🧪 运行测试: ./verify_setup.sh"
|
||||
echo "📝 查看示例: ./curl_examples.sh"
|
||||
@ -40,81 +40,6 @@ services:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
|
||||
# Traefik 反向代理 (使用阿里云镜像)
|
||||
traefik:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/acs/traefik:v3.0
|
||||
container_name: fileshare-traefik
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- traefik
|
||||
command:
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL:-admin@localhost}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
- "--log.level=INFO"
|
||||
- "--accesslog=true"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080" # Traefik dashboard
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./data/letsencrypt:/letsencrypt
|
||||
- ./data/logs/traefik:/var/log/traefik
|
||||
networks:
|
||||
- fileshare-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.dashboard.service=api@internal"
|
||||
- "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_AUTH:-admin:$$2y$$10$$WQiE8P/7W8MZ0GKJYLgVAOUV8D5e6Y7s8rF8w1M9i6QjLqN/3rZ0G}"
|
||||
|
||||
# Redis缓存 (使用阿里云镜像)
|
||||
redis:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/acs/redis:7-alpine
|
||||
container_name: fileshare-redis
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
- ./config/redis.conf:/usr/local/etc/redis/redis.conf:ro
|
||||
networks:
|
||||
- fileshare-network
|
||||
command: redis-server /usr/local/etc/redis/redis.conf
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
reservations:
|
||||
cpus: '0.2'
|
||||
memory: 128M
|
||||
|
||||
# Nginx (可选,用于静态文件服务)
|
||||
nginx:
|
||||
image: registry.cn-hangzhou.aliyuncs.com/acs/nginx:alpine
|
||||
container_name: fileshare-nginx
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./data/uploads:/usr/share/nginx/html/uploads:ro
|
||||
- ./data/logs/nginx:/var/log/nginx
|
||||
networks:
|
||||
- fileshare-network
|
||||
depends_on:
|
||||
- fileshare
|
||||
|
||||
networks:
|
||||
fileshare-network:
|
||||
@ -123,10 +48,3 @@ networks:
|
||||
config:
|
||||
- subnet: 172.20.0.0/16
|
||||
|
||||
volumes:
|
||||
uploads:
|
||||
driver: local
|
||||
logs:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
@ -27,62 +27,9 @@ services:
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.fileshare.rule=Host(`fileshare.localhost`)"
|
||||
- "traefik.http.services.fileshare.loadbalancer.server.port=8000"
|
||||
|
||||
# 可选:使用Traefik作为反向代理(生产环境推荐)
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
container_name: fileshare-traefik
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- traefik # 使用profile控制是否启动
|
||||
command:
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL:-admin@localhost}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "8080:8080" # Traefik dashboard
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./data/letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- fileshare-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.dashboard.service=api@internal"
|
||||
|
||||
# 可选:Redis缓存(用于集群部署时共享会话)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: fileshare-redis
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- redis # 使用profile控制是否启动
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
networks:
|
||||
- fileshare-network
|
||||
command: redis-server --appendonly yes
|
||||
|
||||
networks:
|
||||
fileshare-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
uploads:
|
||||
driver: local
|
||||
logs:
|
||||
driver: local
|
||||
127
fix_paths.py
127
fix_paths.py
@ -1,127 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
路径修复脚本 - 确保所有文件路径正确
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def fix_html_paths():
|
||||
"""修复HTML中的静态资源路径"""
|
||||
html_file = Path("static/index.html")
|
||||
|
||||
if not html_file.exists():
|
||||
print(f"❌ 文件不存在: {html_file}")
|
||||
return False
|
||||
|
||||
print("🔧 修复HTML中的静态资源路径...")
|
||||
|
||||
with open(html_file, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 修复CSS路径
|
||||
content = re.sub(
|
||||
r'<link rel="stylesheet" href="(?!/)([^"]+\.css)"',
|
||||
r'<link rel="stylesheet" href="/static/\1"',
|
||||
content
|
||||
)
|
||||
|
||||
# 修复JS路径
|
||||
content = re.sub(
|
||||
r'<script src="(?!/)([^"]+\.js)"',
|
||||
r'<script src="/static/\1"',
|
||||
content
|
||||
)
|
||||
|
||||
# 修复图片路径(如果有的话)
|
||||
content = re.sub(
|
||||
r'<img src="(?!/)(?!http)([^"]+\.(png|jpg|jpeg|gif|svg))"',
|
||||
r'<img src="/static/\1"',
|
||||
content
|
||||
)
|
||||
|
||||
with open(html_file, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print("✅ HTML路径修复完成")
|
||||
return True
|
||||
|
||||
def check_file_structure():
|
||||
"""检查文件结构是否正确"""
|
||||
print("📁 检查文件结构...")
|
||||
|
||||
required_files = [
|
||||
"app.py",
|
||||
"cli.py",
|
||||
"requirements.txt",
|
||||
"static/index.html",
|
||||
"static/style.css",
|
||||
"static/app.js"
|
||||
]
|
||||
|
||||
missing_files = []
|
||||
|
||||
for file_path in required_files:
|
||||
if not Path(file_path).exists():
|
||||
missing_files.append(file_path)
|
||||
print(f"❌ 缺少文件: {file_path}")
|
||||
else:
|
||||
print(f"✅ 文件存在: {file_path}")
|
||||
|
||||
if missing_files:
|
||||
print(f"\n⚠️ 缺少 {len(missing_files)} 个文件")
|
||||
return False
|
||||
else:
|
||||
print("\n🎉 所有必需文件都存在")
|
||||
return True
|
||||
|
||||
def create_missing_directories():
|
||||
"""创建缺失的目录"""
|
||||
print("📂 创建必需目录...")
|
||||
|
||||
directories = [
|
||||
"static",
|
||||
"uploads",
|
||||
"data",
|
||||
"data/uploads",
|
||||
"data/logs"
|
||||
]
|
||||
|
||||
for dir_path in directories:
|
||||
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
||||
print(f"✅ 目录已确保存在: {dir_path}")
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🔧 文件传输服务路径修复工具")
|
||||
print("=" * 50)
|
||||
|
||||
# 检查当前目录
|
||||
if not Path("app.py").exists():
|
||||
print("❌ 请在fileshare目录中运行此脚本")
|
||||
print("💡 cd fileshare && python fix_paths.py")
|
||||
return False
|
||||
|
||||
# 创建目录
|
||||
create_missing_directories()
|
||||
|
||||
# 检查文件结构
|
||||
if not check_file_structure():
|
||||
print("\n❌ 文件结构不完整,请检查")
|
||||
return False
|
||||
|
||||
# 修复HTML路径
|
||||
if not fix_html_paths():
|
||||
return False
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ 路径修复完成!")
|
||||
print("\n🚀 现在可以启动服务:")
|
||||
print(" python app.py")
|
||||
print("\n🧪 或运行测试:")
|
||||
print(" python test_server.py")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
34
start.sh
34
start.sh
@ -27,33 +27,11 @@ fi
|
||||
|
||||
# 创建数据目录
|
||||
echo "📁 创建数据目录..."
|
||||
mkdir -p data/uploads data/logs data/redis data/letsencrypt
|
||||
mkdir -p data/uploads data/logs
|
||||
|
||||
# 选择启动方式
|
||||
echo "请选择启动方式:"
|
||||
echo "1) 基础模式 (仅文件传输服务)"
|
||||
echo "2) 完整模式 (包含Traefik反向代理)"
|
||||
echo "3) 集群模式 (包含Redis缓存)"
|
||||
read -p "请输入选择 (1-3): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
echo "🚀 启动基础模式..."
|
||||
docker-compose up -d fileshare
|
||||
;;
|
||||
2)
|
||||
echo "🚀 启动完整模式 (包含Traefik)..."
|
||||
docker-compose --profile traefik up -d
|
||||
;;
|
||||
3)
|
||||
echo "🚀 启动集群模式 (包含Redis)..."
|
||||
docker-compose --profile redis up -d
|
||||
;;
|
||||
*)
|
||||
echo "❌ 无效选择,使用基础模式启动..."
|
||||
docker-compose up -d fileshare
|
||||
;;
|
||||
esac
|
||||
# 启动服务
|
||||
echo "🚀 启动文件传输服务..."
|
||||
docker-compose up -d fileshare
|
||||
|
||||
echo
|
||||
echo "⏳ 等待服务启动..."
|
||||
@ -67,10 +45,6 @@ if docker-compose ps | grep -q "Up"; then
|
||||
echo " - API服务: http://localhost:8000"
|
||||
echo " - API文档: http://localhost:8000/docs"
|
||||
|
||||
if [ "$choice" = "2" ]; then
|
||||
echo " - Traefik面板: http://traefik.localhost:8080"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🔧 常用命令:"
|
||||
echo " - 查看日志: docker-compose logs -f"
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
快速测试脚本 - 验证服务是否正常运行
|
||||
"""
|
||||
import requests
|
||||
import time
|
||||
import sys
|
||||
|
||||
def test_server():
|
||||
"""测试服务器是否正常运行"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
print("🧪 文件传输服务测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试首页
|
||||
print("1. 测试首页...")
|
||||
try:
|
||||
response = requests.get(base_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
print("✅ 首页正常 (200)")
|
||||
if "文件传输服务" in response.text:
|
||||
print("✅ 页面内容正确")
|
||||
else:
|
||||
print("⚠️ 页面内容异常")
|
||||
else:
|
||||
print(f"❌ 首页异常 ({response.status_code})")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 无法连接到服务器: {e}")
|
||||
print("💡 请确保服务器已启动: python app.py")
|
||||
return False
|
||||
|
||||
# 测试静态文件
|
||||
print("\n2. 测试静态文件...")
|
||||
static_files = [
|
||||
"/static/style.css",
|
||||
"/static/app.js"
|
||||
]
|
||||
|
||||
for file_path in static_files:
|
||||
try:
|
||||
response = requests.get(f"{base_url}{file_path}", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ {file_path} - 正常")
|
||||
else:
|
||||
print(f"❌ {file_path} - 异常 ({response.status_code})")
|
||||
except Exception as e:
|
||||
print(f"❌ {file_path} - 错误: {e}")
|
||||
|
||||
# 测试API信息
|
||||
print("\n3. 测试API...")
|
||||
try:
|
||||
response = requests.get(f"{base_url}/api", timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print("✅ API信息正常")
|
||||
print(f" 服务: {data.get('service', 'N/A')}")
|
||||
print(f" 版本: {data.get('version', 'N/A')}")
|
||||
else:
|
||||
print(f"❌ API异常 ({response.status_code})")
|
||||
except Exception as e:
|
||||
print(f"❌ API错误: {e}")
|
||||
|
||||
# 测试API文档
|
||||
print("\n4. 测试API文档...")
|
||||
try:
|
||||
response = requests.get(f"{base_url}/docs", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ API文档正常")
|
||||
else:
|
||||
print(f"❌ API文档异常 ({response.status_code})")
|
||||
except Exception as e:
|
||||
print(f"❌ API文档错误: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 测试完成!")
|
||||
print(f"🌐 访问地址: {base_url}")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not test_server():
|
||||
sys.exit(1)
|
||||
124
verify_setup.sh
124
verify_setup.sh
@ -1,124 +0,0 @@
|
||||
#!/bin/bash
|
||||
# 服务验证脚本 - 检查服务是否正确设置和运行
|
||||
|
||||
echo "🔍 文件传输服务验证脚本"
|
||||
echo "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="
|
||||
|
||||
# 检查当前目录
|
||||
if [ ! -f "app.py" ]; then
|
||||
echo "❌ 请在fileshare目录中运行此脚本"
|
||||
echo "💡 cd fileshare && ./verify_setup.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "📂 检查文件结构..."
|
||||
|
||||
# 检查必需文件
|
||||
files=("app.py" "cli.py" "requirements.txt" "static/index.html" "static/style.css" "static/app.js")
|
||||
missing_files=()
|
||||
|
||||
for file in "${files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "✅ $file"
|
||||
else
|
||||
echo "❌ 缺少文件: $file"
|
||||
missing_files+=("$file")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_files[@]} -ne 0 ]; then
|
||||
echo
|
||||
echo "⚠️ 发现 ${#missing_files[@]} 个缺失文件,请先运行: python fix_paths.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🔧 检查Python依赖..."
|
||||
|
||||
# 检查Python是否安装
|
||||
if ! command -v python &> /dev/null && ! command -v python3 &> /dev/null; then
|
||||
echo "❌ Python 未安装"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ Python 已安装"
|
||||
fi
|
||||
|
||||
# 检查pip是否安装
|
||||
if ! command -v pip &> /dev/null && ! command -v pip3 &> /dev/null; then
|
||||
echo "❌ pip 未安装"
|
||||
exit 1
|
||||
else
|
||||
echo "✅ pip 已安装"
|
||||
fi
|
||||
|
||||
# 检查虚拟环境(可选)
|
||||
if [ -n "$VIRTUAL_ENV" ]; then
|
||||
echo "✅ 当前在虚拟环境中: $(basename $VIRTUAL_ENV)"
|
||||
else
|
||||
echo "ℹ️ 未使用虚拟环境(可选)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "📦 检查依赖包..."
|
||||
|
||||
# 检查主要依赖
|
||||
python_cmd="python"
|
||||
if command -v python3 &> /dev/null; then
|
||||
python_cmd="python3"
|
||||
fi
|
||||
|
||||
required_packages=("fastapi" "uvicorn" "httpx" "click" "rich")
|
||||
missing_packages=()
|
||||
|
||||
for package in "${required_packages[@]}"; do
|
||||
if $python_cmd -c "import $package" 2>/dev/null; then
|
||||
echo "✅ $package"
|
||||
else
|
||||
echo "❌ 缺少包: $package"
|
||||
missing_packages+=("$package")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#missing_packages[@]} -ne 0 ]; then
|
||||
echo
|
||||
echo "⚠️ 发现 ${#missing_packages[@]} 个缺失依赖包,请运行:"
|
||||
echo " pip install -r requirements.txt"
|
||||
echo
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🚀 启动测试..."
|
||||
|
||||
# 检查端口是否被占用
|
||||
if command -v lsof &> /dev/null; then
|
||||
if lsof -i :8000 > /dev/null 2>&1; then
|
||||
echo "⚠️ 端口 8000 已被占用"
|
||||
echo "ℹ️ 当前占用进程:"
|
||||
lsof -i :8000
|
||||
echo
|
||||
echo "💡 可以使用其他端口: PORT=8001 python app.py"
|
||||
else
|
||||
echo "✅ 端口 8000 可用"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 提供启动建议
|
||||
echo
|
||||
echo "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "=" "="
|
||||
echo "🎉 验证完成!"
|
||||
echo
|
||||
|
||||
if [ ${#missing_files[@]} -eq 0 ] && [ ${#missing_packages[@]} -eq 0 ]; then
|
||||
echo "✅ 所有检查都通过,可以启动服务了!"
|
||||
echo
|
||||
echo "🚀 推荐启动方式:"
|
||||
echo " 1. 直接启动: python app.py"
|
||||
echo " 2. 开发模式: uvicorn app:app --reload"
|
||||
echo " 3. Docker启动: ./start.sh"
|
||||
echo
|
||||
echo "🌐 启动后访问: http://localhost:8000"
|
||||
echo
|
||||
echo "🧪 启动后可运行测试: python test_server.py"
|
||||
else
|
||||
echo "⚠️ 请先解决上述问题,然后重新运行此脚本"
|
||||
fi
|
||||
Loading…
Reference in New Issue
Block a user