FastAPI๋?
Python์ ์น ํ๋ ์์ํฌํ๋ฉด, ๋ณดํต Django์ Flask๊ฐ ๋ ์ค๋ฅผ ๊ฒ์ด๋ค.
2023๋
๋ถํฐ ์ฑ๋ฅ๊ณผ ๊ฐ๋ฐํธ์์ฑ์ ์ต์ ํ๋ FastAPI๊ฐ ๋ฑ์ฅํ๋ฉด์ ๊ธ ๋ถ์ ์ค์ด๋ค.
FastAPI ํน์ง
- ๋น ๋ฆ: (
Starlette๊ณผPydantic๋๋ถ์)NodeJS๋ฐGo์ ๋๋ฑํ ์ ๋๋ก ๋งค์ฐ ๋์ ์ฑ๋ฅ. ์ฌ์ฉ ๊ฐ๋ฅํ ๊ฐ์ฅ ๋น ๋ฅธ ํ์ด์ฌ ํ๋ ์์ํฌ ์ค ํ๋. - ๋น ๋ฅธ ์ฝ๋ ์์ฑ: ์ฝ 200%์์ 300%๊น์ง ๊ธฐ๋ฅ ๊ฐ๋ฐ ์๋ ์ฆ๊ฐ.
- ์ ์ ๋ฒ๊ทธ: ์ฌ๋(๊ฐ๋ฐ์)์ ์ํ ์๋ฌ ์ฝ 40% ๊ฐ์.
- ์ง๊ด์ : ํ๋ฅญํ ํธ์ง๊ธฐ ์ง์. ๋ชจ๋ ๊ณณ์์ ์๋์์ฑ. ์ ์ ๋๋ฒ๊น ์๊ฐ.
- ์ฌ์: ์ฝ๊ฒ ์ฌ์ฉํ๊ณ ๋ฐฐ์ฐ๋๋ก ์ค๊ณ. ์ ์ ๋ฌธ์ ์ฝ๊ธฐ ์๊ฐ.
- ์งง์: ์ฝ๋ ์ค๋ณต ์ต์ํ. ๊ฐ ๋งค๊ฐ๋ณ์ ์ ์ธ์ ์ฌ๋ฌ ๊ธฐ๋ฅ. ์ ์ ๋ฒ๊ทธ.
- ๊ฒฌ๊ณ ํจ: ์ค๋น๋ ํ๋ก๋์ ์ฉ ์ฝ๋๋ฅผ ์ป์ผ์ญ์์ค. ์๋ ๋ํํ ๋ฌธ์์ ํจ๊ป.
- ํ์ค ๊ธฐ๋ฐ:
API์ ๋ํ (์์ ํ ํธํ๋๋) ๊ฐ๋ฐฉํ ํ์ค ๊ธฐ๋ฐ:OpenAPI(์ด์ ์Swagger๋ก ์๋ ค์ก๋) ๋ฐ JSON ์คํค๋ง.
FastAPI ๊ตฌ๊ธ ํธ๋ ๋ ์กฐ์ฌ
Django๊ฐ ์๋์ ์ด๊ธด ํ๋,Flask์ ๋น๊ตํด๋ณด๋ฉด 2023๋ ๋ถํฐ ๋ง์ ๊ด์ฌ์ ๋ฐ์๋ค๊ณ ๋ณผ ์ ์์ ๊ฒ ๊ฐ๋ค.

ํ์ด์ฌ ์น ํ๋ ์์ํฌ ์ฑ๋ฅ๊ณผ ์ฅ๋จ์ ๋น๊ต (GPT ๋ต๋ณ ์ฒจ๋ถ)
| ์ฑ๋ฅ | ์ฅ์ | ๋จ์ | |
|---|---|---|---|
| FastAPI | ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ์ฌ ๋น ๋ฅธ ์๋์ ๋์ ์ฒ๋ฆฌ๋์ ์ ๊ณตํฉ๋๋ค. ์ด๋ https://docs.python.org/ko/3/library/asyncio.html ๋ฐ https://www.uvicorn.org/๊ณผ ๊ฐ์ ๋น๋๊ธฐ ์น ์๋ฒ์์ ํตํฉ์ผ๋ก ๊ฐ๋ฅํฉ๋๋ค. | - ๋น ๋ฅธ ๊ฐ๋ฐ๊ณผ ํ
์คํธ๋ฅผ ์ํ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. - Pydantic์ ํตํ ์๋ API ๋ฌธ์ ์์ฑ๊ณผ ํ์ ์ฒดํฌ๋ฅผ ์ง์ํ์ฌ API ๋ฌธ์ํ๋ฅผ ์ฉ์ดํ๊ฒ ํฉ๋๋ค. - Swagger UI์ OpenAPI๋ฅผ ์ง์ํ์ฌ API ๋ฌธ์๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. |
- ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ํ ํ์ต ๊ณก์ ์ด ์์ ์ ์์ต๋๋ค. - Flask๋ Django์ ๋นํด ์์ง๊น์ง ์ํ๊ณ๊ฐ ์๋์ ์ผ๋ก ์์ต๋๋ค. |
| Flask | ๊ฐ๋ณ๊ณ ๊ฐ๊ฒฐํ ํ๋ ์์ํฌ์ด๋ฏ๋ก ์๋ ๋ฉด์์ ๋น ๋ฅด๊ฒ ์คํ๋ ์ ์์ต๋๋ค. ํ์ง๋ง ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ์ง ์์ผ๋ฏ๋ก FastAPI๋ณด๋ค๋ ์ฑ๋ฅ์ด ๋ฎ์ ์ ์์ต๋๋ค. | - ๋ฏธ๋๋ฉํ ๋์์ธ์ ๊ฐ์ง๊ณ ์์ด ํ์ํ ๊ธฐ๋ฅ์ ํ์ํ ๋๋ง๋ค ํ์ฅํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. - ๋ค์ํ ํ์ฅ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ์ ์ฐ์ฑ์ ์ ๊ณตํ๋ฉฐ, ๊ฐ๋ฐ์๋ค์ด ์ต์ํ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. |
- ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ์ง ์์ ๋๊ท๋ชจ ๋ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์ ํฉํ์ง ์์ ์ ์์ต๋๋ค. - Flask ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์กฐ๊ฐ ๋ณต์กํด์ง ๊ฒฝ์ฐ ์ ์ง๋ณด์๊ฐ ์ด๋ ค์ธ ์ ์์ต๋๋ค. |
| Django | Django๋ ์ ์ฒด์ ์ผ๋ก ์์ ์ ์ด๊ณ ๋น ๋ฅธ ํ๋ ์์ํฌ์ ๋๋ค. ๊ทธ๋ฌ๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์ FastAPI๋ณด๋ค๋ ์ฑ๋ฅ์ด ๋จ์ด์ง ์ ์์ต๋๋ค. | - ORM, ๊ด๋ฆฌ์ ํจ๋, ์ธ์ฆ ์์คํ
๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ์ฌ ๊ฐ๋ฐ ์์ฐ์ฑ์ ํฅ์์ํต๋๋ค. - ์์ ์ฑ๊ณผ ๋ณด์์ฑ์ด ๋์ผ๋ฉฐ, ์ปค๋ฎค๋ํฐ ๋ฐ ์ํ๊ณ๊ฐ ํ์ฑํ๋์ด ์์ด ๋ค์ํ ๋ฌธ์ ์ ๋ํ ์ง์์ด ์ฉ์ดํฉ๋๋ค. |
- ๋ฌด๊ฑฐ์ด ํ๋ ์์ํฌ์ด๊ธฐ ๋๋ฌธ์ ์์ ํ๋ก์ ํธ๋ ๋น ๋ฅธ ํ๋กํ ํ์ดํ์๋ ์ ํฉํ์ง ์์ ์ ์์ต๋๋ค. - Django์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ๊ณผ ๊ธฐ๋ณธ ์ ๊ณต๋๋ ๊ธฐ๋ฅ ๋๋ฌธ์ ํ๋ก์ ํธ์ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ํ์ํ์ง ์์ ๊ธฐ๋ฅ์ ๊ฐ๊ฒ ๋ ์๋ ์์ต๋๋ค. |
ํ ๋ง๋๋ก ์ ๋ฆฌํ์๋ฉด, FastAPI๋ ๊ฐ๋ฐ๊ณผ ํ
์คํธ๊ฐ ๊ฐ๋จํ๋ฉด์ API ๋ฌธ์๋ฅผ ์ ๊ณตํ๊ณ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ง์ํ๋ ํ๋ ์์ํฌ๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค!
FastAPI๋ก ์น ํ๋ก์ ํธ ๋ง๋ค๊ธฐ
์์ ์์ค : https://wikidocs.net/book/8531
๋
ธ๋ง๋ ์ฝ๋์์ ์ ๊ณตํ๋ “Python์ผ๋ก ์น ์คํฌ๋ํผ ๋ง๋ค๊ธฐ”์์ ์ ๊ณตํ๋ ์์ ๋ฅผ ์์ฉํ์ฌ ๊ฐ๋จํ API ์๋น์ค๋ฅผ ๋ง๋ค์ด๋ณด๋๋ก ํ์.
1. ํ๋ก์ ํธ ์์ฑ ๋ฐ Hello World
- ํ๋ก์ ํธ ์์ฑ :
PyCharm IDE๋ฅผ ์คํํ๊ณ ํ๋ก์ ํธ ์ด๋ฆ๊ณผ ๊ฐ์ํ๊ฒฝ ์ธํ ํ ์์ฑ 
- ๊ฐ์ํ๊ฒฝ ์ ์ฉ ํ์ธ

# ํ์ธ์ด ์๋๋ค๋ฉด, ๋ค์ ๋ช ๋ น์ด๋ฅผ ์ฐจ๋ก๋๋ก ์คํ cd .venv source activate.venv๊ฐ์ ํ๊ฒฝ ํด๋๊ฐ ์์ฑ๋ ๊ฒ ํ์ธ ํ ์ข์ธก ํ๋จ์ ์๋ ํฐ๋ฏธ๋ ์์ด์ฝ์ ํด๋ฆญ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ํฐ๋ฏธ๋์ด ๋ฑ์ฅํ๋๋ฐ(.venv)๊ฐ ๋ฐ๋์ ํ์ธ ๋์ด์ผ ํ๋ค.
- ํจํค์ง ์ค์น
# FastAPI ์ค์น pip install fastapi # FastAPI ํ๋ก๊ทธ๋จ์ ๊ตฌ๋ํ๊ธฐ ์ํ ์น ์๋ฒ Uvicon(๋น๋๊ธฐ ํธ์ถ์ ์ง์ํ๋ ํ์ด์ฌ ์น ์๋ฒ) ์ค์น pip install "uvicorn[standard]"
main.py์์ฑ ๋ฐ ๊ตฌ๋# ํฐ๋ฏธ๋์์ ๋ค์ ๋ช ๋ น์ด ์คํ uvicorn main:app --reload # main:app : main.py์ app์ ์๋ฏธ # --reload : ์๋ฒ ์ฌ์์ ์์ด ๋ฐ์# main.py ์์ฑ from fastapi import FastAPI app = FastAPI() @app.get("/hello") def hello(): return {"message": "hello world"}
- ์ ์ ๋ฐ ์๋์ผ๋ก ์์ฑ ๋
API๋ฌธ์ํ์ธ- http://127.0.0.1:8000/hello ์ ์ ์
main.py์ ์์ฑํ ์๋ต ๊ฒฐ๊ณผ๊ฐ ๋์จ๋ค. 
- http://127.0.0.1:8000/hello ์ ์ ์
- [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs) ์ ์ ์ํ๋ฉด ์คํ ๊ฐ๋ฅํ `Swagger UI` ๊ฐ ์ ๊ณต ๋๊ณ , [http://127.0.0.1:8000/redoc](http://127.0.0.1:8000/redoc) ์ ์ ์ํ๋ฉด ์ฝ๊ธฐ ์ ์ฉ์ธ `Redoc`๋ฌธ์๊ฐ ์๋์ผ๋ก ์ ๊ณต๋๋ค.
> `Spring`์์๋ `API` ๋ฌธ์๋ฅผ ์์ฑํ๋ ค๋ฉด ๊น๋ค๋ก์ด๋ฐ…(`Spring rest docs` ํ
์คํธ ์ฝ๋ ๊ตฌ์ฑ, `Swagger`๋ก ์ธํ `Controller` ์์ค ์นจํฌ๋ก ์ธํ ์ง๊ด์ฑ ๋จ์ด์ง) `FastAPI`๋.. ์.. ๋๋จํ ๊ฒ ๊ฐ๋ค..
>


2. DB & ORM ์ค์
ORMํจํค์ง ์ค์น - ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํ๋SQLAlchemy์ค์นpip install sqlalchemy
database.py์์ฑfrom sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker # Python ๊ธฐ๋ณธ ํจํค์ง์ ํฌํจ ๋์ด์๋ sqlite ์ฌ์ฉ SQLALCHEMY_DATABASE_URL = "sqlite:///./fastapi-scrap.db" engine = create_engine( SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() # DB ์ธ์ ๊ฐ์ฒด ํจ์ def get_db(): db = SessionLocal() try: yield db finally: db.close()
- ๋ชจ๋ธ ์์ฑ - ์ฑ์ฉ์ ๋ณด ๋๋ฉ์ธ
2) models.py ์์ฑerDiagram employ{ Integer id String platform String keyword String company_name String position DateTime create_date } # models.py from sqlalchemy import Column, Integer, String, DateTime from database import Base class Employ(Base): __tablename__ = "employ" id = Column(Integer, primary_key=True) platform = Column(String, nullable=False) keyword = Column(String, nullable=False) company_name = Column(String, nullable=False) position = Column(String, nullable=False) create_date = Column(DateTime, nullable=False)- 1) ์ฑ์ฉ์ ๋ณด ๋ชจ๋ธ ์ค๊ณ
alembic์ค์ 1) ํฐ๋ฏธ๋์์alembicํจํค์ง ์ค์น ๋ฐ ์ด๊ธฐํ2) ์ด๊ธฐํ ์, ์๊ธด ๋ง์ด๊ทธ๋ ์ด์ ์ค์ ํ์ผ ์์ alembic.ini,migrations/env.py# alembic.ini ์์ ... sqlalchemy.url = sqlite:///./fastapi-study.db # ์์ ...
3) ๋ฆฌ๋น์ ํ์ผ ์์ฑ4)# migrations/env.py ์์ ... import models ... target_metadata = models.Base.metadata ...fastapi-scrap.db์์ฑ ํ์ธ# ๋ฆฌ๋น์ ํ์ผ ์์ฑ - ํ ์ด๋ธ ์คํฌ๋ฆฝํธ ๋ฌธ ์์ฑ alembic revision --autogenerate # ๋ฆฌ๋น์ ํ์ผ ์คํ - ํ ์ด๋ธ ์คํฌ๋ฆฝํธ ์ ์ฉ alembic upgrade head
# alembic ํจํค์ง ์ค์น pip install alembic # alembic ์ด๊ธฐํ alembic init migrationsalembic์ด๋?SQLAlchemy์์ ์ ๊ณตํ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ง์ด๊ทธ๋ ์ด์ ๋๊ตฌ์ด๋ค.

3. API ์์ฑ
- ๋๋ฉ์ธ ๋๋ ํ ๋ฆฌ ์์ฑ -
domain/employ 
Pydantic์ ํ์ฉํemploy_schema.py์์ฑfrom datetime import datetime from pydantic import BaseModel, field_validator # ์กฐํ์, ์๋ต ์คํ ์ ์ class Employ(BaseModel): id: int platform: str keyword: str company_name: str position: str create_date: datetime # ๋ฑ๋ก์, ์์ฒญ ์คํ ์ ์ class EmployCreate(BaseModel): keyword: str company_name: str position: str # ๋น ๋ฌธ์์ด ๊ฒ์ฆ @field_validator('keyword', 'company_name', 'position') def not_empty(cls, v): if not v or not v.strip(): raise ValueError("Required value") return vPydantic์ด๋, ์ ์ถ๋ ฅ ์คํ์ ์ ์ํ๊ณ ๊ฐ์ ๊ฒ์ฆํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
employ_crud.py์์ฑ - ์ค์ ์ฟผ๋ฆฌ ์ ์ฉ ๊ตฌํ ๋ถfrom datetime import datetime from models import Employ from sqlalchemy.orm import Session from domain.employ.employ_schema import EmployCreate # ์ฑ์ฉ ์ ๋ณด ์ ์ฒด ์กฐํ def employ_list(db: Session): return db.query(Employ) \ .order_by(Employ.create_date.desc()) \ .all() # ์ฑ์ฉ ์ ๋ณด ๋ฑ๋ก def employ_create(db: Session, create_request: EmployCreate, platform): db.add(Employ(platform=platform, keyword=create_request.keyword, company_name=create_request.company_name, position=create_request.position, create_date=datetime.now())) db.commit()
APIRouter์ ํ์ฉํemploy_router.py์์ฑfrom fastapi import APIRouter, Depends from sqlalchemy.orm import Session from database import get_db # ์์์ ์ ์ํ employ_schema, employ_crud ์ฐธ์กฐ from domain.employ import employ_schema, employ_crud # prefix ์ค์ router = APIRouter( prefix="/api/employ" ) # ์ฑ์ฉ ์ ๋ณด ์ ์ฒด ์กฐํ API @router.get("/list", response_model=list[employ_schema.Employ]) # employ_schema.py ์์ ์ ์ํ ์๋ต ์คํ ๊ธฐ์ฌ def employ_list(db: Session = Depends(get_db)): return employ_crud.employ_list(db) # ์ฑ์ฉ ์ ๋ณด ์๋ ๋ฑ๋ก API @router.post("/manual/create") def employ_manual_create(create_request: employ_schema.EmployCreate, # employ_schema.py ์์ ์ ์ํ ์์ฒญ ์คํ ํ๋ผ๋ฏธํฐ db: Session = Depends(get_db)): employ_crud.employ_create(db, create_request, "MANUAL")router๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌFastAPI์ฑ์ ๋ฑ๋กํด์ผ๋ง ๋ผ์ฐํ ๊ธฐ๋ฅ ์ ์ ๋์
router๋ฅผmain.py์ ๋ฑ๋กfrom fastapi import FastAPI from domain.employ import employ_router app = FastAPI() # ๋ผ์ฐํฐ ๋ฑ๋ก app.include_router(employ_router.router)
- ์คํ ํ
Swagger UI๋ฅผ ์ด์ฉํAPIํ ์คํธuvicorn main:app --reload - 1)
uvicorn๋ช ๋ น์ด๋ก ์๋ฒ ์ฌ์คํ
2) http://127.0.0.1:8000/docs ์ ์ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ API 2๊ฐ๊ฐ ๋ณด์ธ๋ค.

3) ์ฑ์ฉ ์ ๋ณด ๋ฑ๋ก API ํ
์คํธ
Try it out์ ํด๋ฆญํ๋ฉด ํ ์คํธ๋ฅผ ์งํ ํ ์ ์๋ค.

Request Body๋ฅผ ์์ฑ ํExecute๋ฒํผ์ ๋๋ฅด๋ฉด API ํธ์ถ์ด ๋๋ค.

4) ๋ฑ๋กํ ์ฑ์ฉ ์ ๋ณด๋ฅผ ์ฑ์ฉ ์ ๋ณด ์กฐํ API๋ฅผ ํตํด ํ์ธ
- ์์ ๊ฐ์ด ์ฑ์ฉ ์ ๋ณด ์กฐํ
API๋ฅผTry it out๋ฒํผ๊ณผExecute๋ฒํผ์ ์ฐจ๋ก๋๋ก ํด๋ฆญ ํ๋ฉด ๋ค์ ์ด๋ฏธ์ง์ ๊ฐ์ด ๊ฒฐ๊ณผ๋ฅผ ํ์ธ ํ ์ ์๋ค.

๋ง๋ฌด๋ฆฌ
์์ ๊ณผ์ ์์ ์์ธํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ ์ธ๊ธํ์ง ์์๋ค. (์ ์๋ ํ์ด์ฌ ์ด๋ณด๋ผ… ๐น)
์์ธํ ๋ด์ฉ์ ์๋ [์ฐธ๊ณ ] ๋งํฌ๋ฅผ ํตํด ํ์ธํ๊ธธ ๋ฐ๋๋ค.
FastAPI ์น ํ๋ ์์ํฌ๋ฅผ ํ์ฉํ๋ API๋ ๊ณต์ฅ์ฒ๋ผ ์ฐ์ด ๋ผ ์ ์์ ๊ฒ ๊ฐ๋ค.
๋๋ฌด ๊ฐ๋จํ๊ธฐ๋ ํ๊ณ .. ๋ฌธ์๋ ์๋ํ ํด์ฃผ๋.. ๋๋ฌด ํธ๋ฆฌํ์ง ์๋๊ฐ!!!???

๋ถ๋์ด ๋ง์์ง ๊ด๊ณ๋ก ์คํฌ๋ํํ ์ฑ์ฉ ์ ๋ณด ๋ฑ๋ก์ API๋ก ๊ตฌ์ฑํ๋ ์์ ๋ ๋ค์ ๊ธ์์ ๋ต๋๋ก ํ๊ฒ ๋ค. ๐๐ป
https://github.com/discphy/fastapi-scrap
[์ฐธ๊ณ ]
https://github.com/tiangolo/fastapi
https://dasima.co.kr/125/fastapi๋-๋ฌด์์ธ๊ฐ-fastapi-์ฌ์ฉ์ค๋ช ์-01/
'๐ฑBackend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ํ์ด์ฌ FastAPI๋ฅผ ์ด์ฉํ API ํ๋ก์ ํธ ๋ง๋ค๊ธฐ (1) - ์คํฌ๋ํ API ๋ง๋ค๊ธฐ (0) | 2024.11.26 |
|---|---|
| ํ์ด์ฌ ์น ์คํฌ๋ํ - ๋ฉ๋ก ์์ ์ถ์ถ (2) | 2024.03.29 |
| SNS ๋ก๊ทธ์ธ - Spring OAuth2 Client (0) | 2023.07.24 |
| ๋๋ค๋ฅผ ์ฌ์ฉํด์ผ ๋๋ ์ด์ (0) | 2023.06.05 |
| ์คํ๋ง API ๋น๋๊ธฐ ๋ ผ๋ธ๋กํน ๋ฐฉ์ ํธ์ถ (0) | 2023.03.27 |