diff --git a/README.md b/README.md index e192b45..bb0da93 100644 --- a/README.md +++ b/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(如需要) ## 开发 diff --git a/cli.py b/cli.py deleted file mode 100644 index 0cb8474..0000000 --- a/cli.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/curl_examples.sh b/curl_examples.sh deleted file mode 100755 index 418e92e..0000000 --- a/curl_examples.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/demo.sh b/demo.sh deleted file mode 100755 index 54b3293..0000000 --- a/demo.sh +++ /dev/null @@ -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" \ No newline at end of file diff --git a/docker-compose.aliyun.yml b/docker-compose.aliyun.yml index 5237a53..98ee564 100644 --- a/docker-compose.aliyun.yml +++ b/docker-compose.aliyun.yml @@ -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 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 210df72..8a167b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/fix_paths.py b/fix_paths.py deleted file mode 100755 index 6b81b2f..0000000 --- a/fix_paths.py +++ /dev/null @@ -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' /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 \ No newline at end of file