feat(api): add rate limiting for API requests

This commit is contained in:
MingxuanGame
2025-08-28 13:51:44 +00:00
parent c7f6c76b0f
commit aa239a81fa
5 changed files with 45 additions and 3 deletions

View File

@@ -28,6 +28,7 @@ DEBUG=false
CORS_URLS='[]'
SERVER_URL="http://localhost:8000"
FRONTEND_URL=
ENABLE_RATE_LIMIT=true
# SignalR Settings
SIGNALR_NEGOTIATE_TIMEOUT=30

View File

@@ -93,6 +93,7 @@ class Settings(BaseSettings):
cors_urls: list[HttpUrl] = []
server_url: HttpUrl = HttpUrl("http://localhost:8000")
frontend_url: HttpUrl | None = None
enable_rate_limit: bool = True
@property
def web_url(self):

30
main.py
View File

@@ -4,7 +4,7 @@ from contextlib import asynccontextmanager
from pathlib import Path
from app.config import settings
from app.dependencies.database import engine, redis_client
from app.dependencies.database import engine, get_redis, redis_client
from app.dependencies.fetcher import get_fetcher
from app.dependencies.scheduler import start_scheduler, stop_scheduler
from app.log import logger
@@ -37,16 +37,19 @@ from app.service.osu_rx_statistics import create_rx_statistics
from app.service.redis_message_system import redis_message_system
from app.utils import bg_tasks, utcnow
from fastapi import FastAPI, HTTPException, Request
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter
import sentry_sdk
@asynccontextmanager
async def lifespan(app: FastAPI):
# on startup
await FastAPILimiter.init(get_redis())
await get_fetcher() # 初始化 fetcher
await init_geoip() # 初始化 GeoIP 数据库
await create_rx_statistics()
@@ -80,7 +83,7 @@ async def lifespan(app: FastAPI):
await redis_client.aclose()
desc = """osu! API 模拟服务器,支持 osu! API v1, v2 和 osu!lazer 的绝大部分功能。
desc = f"""osu! API 模拟服务器,支持 osu! API v1, v2 和 osu!lazer 的绝大部分功能。
## 端点说明
@@ -100,6 +103,19 @@ v2 API 采用 OAuth 2.0 鉴权,支持以下鉴权方式:
v1 API 采用 API Key 鉴权,将 API Key 放入 Query `k` 中。
{
'''
## 速率限制
所有 API 请求均受到速率限制,具体限制规则如下:
- 每分钟最多可以发送 1200 个请求
- 突发请求限制为每秒最多 200 个请求
'''
if settings.enable_rate_limit
else ""
}
## 参考
- v2 API 文档:[osu-web 文档](https://osu.ppy.sh/docs/index.html)
@@ -136,6 +152,14 @@ app = FastAPI(
lifespan=lifespan,
description=desc,
)
if settings.enable_rate_limit:
app.router.dependencies.extend(
[
Depends(RateLimiter(times=1200, minutes=1)),
Depends(RateLimiter(times=200, seconds=1)),
]
)
app.include_router(api_v2_router)
app.include_router(api_v1_router)

View File

@@ -13,6 +13,7 @@ dependencies = [
"bcrypt>=4.1.2",
"cryptography>=41.0.7",
"fastapi>=0.104.1",
"fastapi-limiter>=0.1.6",
"httpx>=0.28.1",
"loguru>=0.7.3",
"maxminddb>=2.8.2",

15
uv.lock generated
View File

@@ -459,6 +459,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/47/d63c60f59a59467fda0f93f46335c9d18526d7071f025cb5b89d5353ea42/fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565", size = 95631, upload-time = "2025-07-11T16:22:30.485Z" },
]
[[package]]
name = "fastapi-limiter"
version = "0.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "fastapi" },
{ name = "redis" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7f/99/c7903234488d4dca5f9bccb4f88c2f582a234f0dca33348781c9cf8a48c6/fastapi_limiter-0.1.6.tar.gz", hash = "sha256:6f5fde8efebe12eb33861bdffb91009f699369a3c2862cdc7c1d9acf912ff443", size = 8307, upload-time = "2024-01-05T09:14:48.628Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cd/b5/6f6b4d18bee1cafc857eae12738b3a03b7d1102b833668be868938c57b9d/fastapi_limiter-0.1.6-py3-none-any.whl", hash = "sha256:2e53179a4208b8f2c8795e38bb001324d3dc37d2800ff49fd28ec5caabf7a240", size = 15829, upload-time = "2024-01-05T09:14:47.613Z" },
]
[[package]]
name = "filelock"
version = "3.19.1"
@@ -541,6 +554,7 @@ dependencies = [
{ name = "bcrypt" },
{ name = "cryptography" },
{ name = "fastapi" },
{ name = "fastapi-limiter" },
{ name = "httpx" },
{ name = "loguru" },
{ name = "maxminddb" },
@@ -581,6 +595,7 @@ requires-dist = [
{ name = "bcrypt", specifier = ">=4.1.2" },
{ name = "cryptography", specifier = ">=41.0.7" },
{ name = "fastapi", specifier = ">=0.104.1" },
{ name = "fastapi-limiter", specifier = ">=0.1.6" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "loguru", specifier = ">=0.7.3" },
{ name = "maxminddb", specifier = ">=2.8.2" },