錯誤路徑: /api/file/aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
症狀: 點擊「📥 下載檔案」按鈕後,無法下載檔案
- 下載任務未完成 (status != 'completed')
- 任務 ID 不存在
- 檔案路徑為
None或空字串 - 檔案路徑是相對路徑,在容器中無法找到
- 檔案在下載後被刪除或移動
- 檔案系統是臨時的 (ephemeral)
- 權限問題
- 多個 worker 之間無法共享記憶體狀態
位置: app_pytubefix.py - download_file() 函數
@app.route('/api/file/<task_id>')
def download_file(task_id):
print(f'📥 下載請求: task_id={task_id}')
if task_id not in download_tasks:
print(f'❌ 任務不存在: {task_id}')
print(f' 現有任務: {list(download_tasks.keys())}')
return jsonify({'error': '任務不存在'}), 404
task = download_tasks[task_id]
print(f'📋 任務狀態: {task["status"]}')
if task['status'] != 'completed':
print(f'⚠️ 下載未完成: status={task["status"]}')
return jsonify({'error': f'下載未完成 (狀態: {task["status"]})'}, 400)
file_path = task.get('file_path')
print(f'📁 檔案路徑: {file_path}')
if not os.path.exists(file_path):
print(f'❌ 檔案不存在: {file_path}')
print(f' downloads 目錄內容: {os.listdir(DOWNLOAD_FOLDER)}')
return jsonify({'error': f'檔案不存在'}), 404
print(f'✅ 開始傳送檔案: {task["filename"]}')好處: 可以在 Railway 日誌中看到詳細的錯誤訊息
位置: app_pytubefix.py - 初始化和下載完成
# 初始化時
DOWNLOAD_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), 'downloads'))
print(f'📁 下載目錄: {DOWNLOAD_FOLDER}')
# 下載完成時
download_tasks[task_id]['file_path'] = os.path.abspath(file_path) # 絕對路徑
print(f' 路徑: {download_tasks[task_id]["file_path"]}')
print(f' 存在: {os.path.exists(download_tasks[task_id]["file_path"])}')好處: 確保路徑在容器中正確解析
try:
return send_file(
file_path,
as_attachment=True,
download_name=task['filename']
)
except Exception as e:
print(f'❌ 傳送檔案失敗: {e}')
traceback.print_exc()
return jsonify({'error': f'傳送檔案失敗: {str(e)}'}), 500好處: 捕獲 send_file 的錯誤
前往 Railway Dashboard → Deployments → 等待狀態變為 "Active"
操作步驟:
- 貼上 YouTube 網址
- 選擇音訊或影片
- 開始下載
- 等待 100%
- 點擊「📥 下載檔案」
同時查看 Railway 日誌 (View Logs):
📁 下載目錄: /app/downloads
✅ 下載目錄已存在: /app/downloads
# 下載時
✅ 下載完成: test_video.mp3
檔案大小: 5.82 MB
檔案格式: .mp3
✅ 任務完成!
檔案: test_video.mp3
路徑: /app/downloads/test_video.mp3
存在: True
# 下載檔案時
📥 下載請求: task_id=aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
📋 任務狀態: completed
📁 檔案路徑: /app/downloads/test_video.mp3
✅ 開始傳送檔案: test_video.mp3
錯誤 1: 任務不存在
📥 下載請求: task_id=aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
❌ 任務不存在: aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
現有任務: []
原因: 多個 worker 導致記憶體不共享
解決: 設定 --workers 1 或使用資料庫/Redis 儲存任務狀態
錯誤 2: 下載未完成
📥 下載請求: task_id=aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
📋 任務狀態: downloading
⚠️ 下載未完成: status=downloading
原因: 下載還在進行中 解決: 等待下載完成
錯誤 3: 檔案不存在
📥 下載請求: task_id=aace66ae-f814-4fa0-8a7b-bc0f1f8c10dc
📋 任務狀態: completed
📁 檔案路徑: /app/downloads/test_video.mp3
❌ 檔案不存在: /app/downloads/test_video.mp3
downloads 目錄內容: []
原因: 檔案在下載後消失 (容器重啟或檔案系統問題) 解決: 檢查容器是否重啟,或使用外部儲存 (S3, Cloudinary)
症狀: 任務在一個 worker 創建,但在另一個 worker 查詢時找不到
當前設定: --workers 2 (Dockerfile 和 start.sh)
解決方案 1: 使用單一 Worker
修改 Dockerfile:
CMD gunicorn app_pytubefix:app --bind 0.0.0.0:$PORT --workers 1 --timeout 300修改 start.sh:
exec gunicorn app_pytubefix:app --bind 0.0.0.0:$PORT --workers 1 --timeout 300優點: 簡單,記憶體狀態一致 缺點: 無法同時處理多個請求
解決方案 2: 使用資料庫儲存任務狀態 (未來改進)
使用 SQLite 或 Railway PostgreSQL:
# 替代 download_tasks = {} 記憶體字典
# 使用資料庫表儲存任務狀態- Railway 部署狀態為 "Active"
- 日誌中有
📁 下載目錄: /app/downloads - 日誌中有
✅ 下載目錄已存在 - 測試下載音訊
- 日誌中有
✅ 任務完成! - 日誌中顯示
存在: True - 點擊下載檔案
- 日誌中有
📥 下載請求 - 日誌中有
✅ 開始傳送檔案 - 檔案成功下載到瀏覽器
- 截圖 Railway 日誌 (包含錯誤訊息)
- 記錄 task_id
- 檢查是否有多個 worker
- 嘗試設定
--workers 1
成功下載流程:
- 開始下載 → 創建任務,返回 task_id
- 下載中 → 顯示進度 0% → 100%
- 轉換 (音訊) → 顯示 "轉換為 MP3..."
- 完成 → 狀態變為 "completed"
- 點擊下載 → 瀏覽器開始下載 .mp3 或 .mp4 檔案
日誌確認:
✅ 任務完成!
檔案: test_video.mp3
路徑: /app/downloads/test_video.mp3
存在: True
📥 下載請求: task_id=xxx
✅ 開始傳送檔案: test_video.mp3
Commit: 0ce5454
修改內容:
- ✅ 添加詳細下載日誌
- ✅ 使用絕對路徑儲存檔案
- ✅ 增強錯誤處理
- ✅ 添加檔案存在性檢查
- ✅ 添加 downloads 目錄內容輸出
建立時間: 2025-10-09
狀態: ✅ 修復已推送
下一步: 等待部署,查看 Railway 日誌診斷問題