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='[]' CORS_URLS='[]'
SERVER_URL="http://localhost:8000" SERVER_URL="http://localhost:8000"
FRONTEND_URL= FRONTEND_URL=
ENABLE_RATE_LIMIT=true
# SignalR Settings # SignalR Settings
SIGNALR_NEGOTIATE_TIMEOUT=30 SIGNALR_NEGOTIATE_TIMEOUT=30

View File

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

30
main.py
View File

@@ -4,7 +4,7 @@ from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from app.config import settings 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.fetcher import get_fetcher
from app.dependencies.scheduler import start_scheduler, stop_scheduler from app.dependencies.scheduler import start_scheduler, stop_scheduler
from app.log import logger 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.service.redis_message_system import redis_message_system
from app.utils import bg_tasks, utcnow 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.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi_limiter import FastAPILimiter
from fastapi_limiter.depends import RateLimiter
import sentry_sdk import sentry_sdk
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
# on startup # on startup
await FastAPILimiter.init(get_redis())
await get_fetcher() # 初始化 fetcher await get_fetcher() # 初始化 fetcher
await init_geoip() # 初始化 GeoIP 数据库 await init_geoip() # 初始化 GeoIP 数据库
await create_rx_statistics() await create_rx_statistics()
@@ -80,7 +83,7 @@ async def lifespan(app: FastAPI):
await redis_client.aclose() 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` 中。 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) - v2 API 文档:[osu-web 文档](https://osu.ppy.sh/docs/index.html)
@@ -136,6 +152,14 @@ app = FastAPI(
lifespan=lifespan, lifespan=lifespan,
description=desc, 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_v2_router)
app.include_router(api_v1_router) app.include_router(api_v1_router)

View File

@@ -13,6 +13,7 @@ dependencies = [
"bcrypt>=4.1.2", "bcrypt>=4.1.2",
"cryptography>=41.0.7", "cryptography>=41.0.7",
"fastapi>=0.104.1", "fastapi>=0.104.1",
"fastapi-limiter>=0.1.6",
"httpx>=0.28.1", "httpx>=0.28.1",
"loguru>=0.7.3", "loguru>=0.7.3",
"maxminddb>=2.8.2", "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" }, { 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]] [[package]]
name = "filelock" name = "filelock"
version = "3.19.1" version = "3.19.1"
@@ -541,6 +554,7 @@ dependencies = [
{ name = "bcrypt" }, { name = "bcrypt" },
{ name = "cryptography" }, { name = "cryptography" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "fastapi-limiter" },
{ name = "httpx" }, { name = "httpx" },
{ name = "loguru" }, { name = "loguru" },
{ name = "maxminddb" }, { name = "maxminddb" },
@@ -581,6 +595,7 @@ requires-dist = [
{ name = "bcrypt", specifier = ">=4.1.2" }, { name = "bcrypt", specifier = ">=4.1.2" },
{ name = "cryptography", specifier = ">=41.0.7" }, { name = "cryptography", specifier = ">=41.0.7" },
{ name = "fastapi", specifier = ">=0.104.1" }, { name = "fastapi", specifier = ">=0.104.1" },
{ name = "fastapi-limiter", specifier = ">=0.1.6" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "loguru", specifier = ">=0.7.3" }, { name = "loguru", specifier = ">=0.7.3" },
{ name = "maxminddb", specifier = ">=2.8.2" }, { name = "maxminddb", specifier = ">=2.8.2" },