feat(cache): add 30-day browser cache for uploaded images (middleware + S3 CacheControl)

This commit is contained in:
2026-07-18 08:23:13 +08:00
parent 54d778971d
commit 2dc68ff408
3 changed files with 16 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
@echo off @echo off
cd /d "%~dp0" cd /d "%~dp0"
call .venv\Scripts\activate.bat call .venv\Scripts\activate.bat
python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload @rem python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
pause pause

View File

@@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from app.config import UPLOAD_DIR from app.config import UPLOAD_DIR
from app.database import init_db from app.database import init_db
@@ -13,6 +14,18 @@ UPLOAD_DIR.mkdir(exist_ok=True)
app.mount("/static", StaticFiles(directory=str(Path(__file__).resolve().parent / "static")), name="static") app.mount("/static", StaticFiles(directory=str(Path(__file__).resolve().parent / "static")), name="static")
app.mount("/uploads", StaticFiles(directory=str(UPLOAD_DIR)), name="uploads") app.mount("/uploads", StaticFiles(directory=str(UPLOAD_DIR)), name="uploads")
# Browser cache: 30 days for uploaded images (they never change)
class CacheControlMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
response: Response = await call_next(request)
if request.url.path.startswith("/uploads/"):
response.headers["Cache-Control"] = "public, max-age=2592000, immutable"
return response
app.add_middleware(CacheControlMiddleware)
app.include_router(web.router) app.include_router(web.router)
app.include_router(api.router) app.include_router(api.router)

View File

@@ -103,6 +103,7 @@ class S3Storage:
def put(self, key: str, data: bytes, content_type: str = "image/jpeg") -> str: def put(self, key: str, data: bytes, content_type: str = "image/jpeg") -> str:
self._client.put_object( self._client.put_object(
Bucket=self.bucket, Key=key, Body=data, ContentType=content_type, Bucket=self.bucket, Key=key, Body=data, ContentType=content_type,
CacheControl="public, max-age=2592000, immutable",
) )
return key return key