feat(storage): expose a path to access local storage

This commit is contained in:
MingxuanGame
2025-08-12 05:23:16 +00:00
parent 449bda0a84
commit a488257acd
9 changed files with 52 additions and 15 deletions

View File

@@ -1,4 +1,5 @@
.venv/
.ruff_cache/
.vscode/
storage/
replays/

View File

@@ -18,6 +18,8 @@ ACCESS_TOKEN_EXPIRE_MINUTES=1440
# 服务器地址
HOST="0.0.0.0"
PORT=8000
# 服务器 URL
SERVER_URL="http://localhost:8000"
# 调试模式,生产环境请设置为 false
DEBUG=false
# 私有 API 密钥,用于前后端 API 调用,使用 openssl rand -hex 32 生成
@@ -37,7 +39,6 @@ SIGNALR_PING_INTERVAL=15
FETCHER_CLIENT_ID=""
FETCHER_CLIENT_SECRET=""
FETCHER_SCOPES=public
FETCHER_CALLBACK_URL="http://localhost:8000/fetcher/callback"
# 日志设置
LOG_LEVEL="INFO"

1
.gitignore vendored
View File

@@ -210,5 +210,6 @@ bancho.py-master/*
.vscode/settings.json
# runtime file
storage/
replays/
osu-master/*

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from enum import Enum
from typing import Annotated, Any
from pydantic import Field, ValidationInfo, field_validator
from pydantic import Field, HttpUrl, ValidationInfo, field_validator
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
@@ -65,6 +65,7 @@ class Settings(BaseSettings):
port: int = 8000
debug: bool = False
private_api_secret: str = "your_private_api_secret_here"
server_url: HttpUrl = HttpUrl("http://localhost:8000")
# SignalR 设置
signalr_negotiate_timeout: int = 30
@@ -74,7 +75,10 @@ class Settings(BaseSettings):
fetcher_client_id: str = ""
fetcher_client_secret: str = ""
fetcher_scopes: Annotated[list[str], NoDecode] = ["public"]
fetcher_callback_url: str = "http://localhost:8000/fetcher/callback"
@property
def fetcher_callback_url(self) -> str:
return f"{self.server_url}fetcher/callback"
# 日志设置
log_level: str = "INFO"

View File

@@ -4,6 +4,7 @@ from app.signalr import signalr_router as signalr_router
from .auth import router as auth_router
from .fetcher import fetcher_router as fetcher_router
from .file import file_router as file_router
from .private import private_router as private_router
from .v2.router import router as api_v2_router
@@ -11,6 +12,7 @@ __all__ = [
"api_v2_router",
"auth_router",
"fetcher_router",
"file_router",
"private_router",
"signalr_router",
]

26
app/router/file.py Normal file
View File

@@ -0,0 +1,26 @@
from __future__ import annotations
from app.dependencies.storage import get_storage_service
from app.storage import LocalStorageService, StorageService
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
file_router = APIRouter(prefix="/file")
@file_router.get("/{path:path}")
async def get_file(path: str, storage: StorageService = Depends(get_storage_service)):
if not isinstance(storage, LocalStorageService):
raise HTTPException(404, "Not Found")
if not await storage.is_exists(path):
raise HTTPException(404, "Not Found")
try:
return FileResponse(
path=storage._get_file_path(path),
media_type="application/octet-stream",
filename=path.split("/")[-1],
)
except FileNotFoundError:
raise HTTPException(404, "Not Found")

View File

@@ -1,7 +1,5 @@
from __future__ import annotations
from app.signalr import signalr_router as signalr_router
from . import ( # pyright: ignore[reportUnusedImport] # noqa: F401
beatmap,
beatmapset,

View File

@@ -2,6 +2,8 @@ from __future__ import annotations
from pathlib import Path
from app.config import settings
from .base import StorageService
import aiofiles
@@ -75,4 +77,4 @@ class LocalStorageService(StorageService):
return full_path.exists() and full_path.is_file()
async def get_file_url(self, file_path: str) -> str:
return str(self.storage_path / file_path)
return f"{settings.server_url}file/{file_path.lstrip('/')}"

14
main.py
View File

@@ -12,6 +12,7 @@ from app.router import (
api_v2_router,
auth_router,
fetcher_router,
file_router,
private_router,
signalr_router,
)
@@ -38,19 +39,20 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="osu! API 模拟服务器", version="1.0.0", lifespan=lifespan)
app.include_router(api_v2_router)
app.include_router(signalr_router)
app.include_router(fetcher_router)
app.include_router(file_router)
app.include_router(auth_router)
app.include_router(private_router)
# CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_origins=[str(settings.server_url)],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(api_v2_router)
app.include_router(signalr_router)
app.include_router(fetcher_router)
app.include_router(auth_router)
app.include_router(private_router)
@app.get("/")