본문 바로가기

명사 美 비격식 (무리 중에서) 아주 뛰어난[눈에 띄는] 사람[것]

Personal/SK 네트웍스 AI 캠프

SK 네트웍스 AI 캠프 - 3_초거대언어모델(LLM) - Day39_LLM의 개념과 API

LLM

기존 NLP모델은 감성분석, 번역모델, 요약모델, 질문답변 모델 등 작업별로 따로 학습해야했지만 GPT-3이후 LLM은 프롬프트만 바꿔도 여러 작업을 수행할 수 있게 되었다. 

 

Pre-training

LLM을 만들때 가장 먼저 수행하는 대규모 사전 학습단계, 모델은 인터넷, 문서, 책, 코드, 위키문서 등 대량의 텍스트를 학습하면서 정답패턴을 예측한다. 이 과정을 매우 큰 데이터로 반복하면 모델은 문장구조이해, 단어의미관계학습, 문맥파악, 상식적표현, 코드패턴, 질문답변 구조를 학습 할수 있다. 

 

LLM 종류

Decoder-only모델: GPT 계열 이전 토큰을 보고 다음 토큰 생성

Encoder-only모델: BERT 계열 문장을 이해하고 분류

Encoder-decoder 모델 : T5, BART 입력문장을 다른 문장으로 변환

instruction-tuned모델: InstructGPT, ChatGPT 계열 사람의 지시를 잘 따르도록 추가학습

Multimodal 모델: GPT-4o계열, 텍스트, 이미지, 음성도 처리

https://standout.tistory.com/1879

 

LLM의 정의와 LLM 종류

LLMLarge Language Model, 대규모 언어모델GPT-3 는 2020 년에 공개된 대표적인 초거대언어모델이며, 1,750 억 개 파라미터를 가진 자기회귀 언어 모델기존 NLP모델은 감성분석, 번역모델, 요약모델, 질문답

standout.tistory.com

 

 

GPT계열은 생성형 작업에 강해 글쓰기, 답변, 코드작성, 요약, 번역, 챗봇에 강하다.

BERT 계열은 이해기반작업에 강해 문장분류, 감정분석, 개체명 인식, 문장유사도, 검색랭킹에 강하다. 

 

In-context Learning

프롬프트 안에 설명이나 예시를 넣어 원하는 작업을 수행하게 하는 방식

 

LLM의 Hallucination 사실이 아닌 내용을 그럴듯하게 생성하는 현상

RAG를 적용하거나 공식문서 검색후 답변, DB 조회결과기반 답변, 출처표시, Function Calling, 검증로직추가, 응답 후처리, 사람검토단계 추가 등으로 보완할 수 있다 .

https://standout.tistory.com/1876

 

RAG(Retrieval-Augmented Generation)와 LLM의 Hallucination(환각): 외부 문서를 검색한 후 검색 결과를 바탕으

RAG(Retrieval-Augmented Generation)기업은 범용 언어모델을 그대로 사용하는 것이 아니라 사내 문서, 정보, 고객데이터 등을 추가해 기업전용 AI시스템을 구축하며 LLM은 이때 존재하지않는 정보를 생성

standout.tistory.com

 

 

프롬프트

LLM에게 전달하는 입력 지시문

https://standout.tistory.com/1875

 

프롬프트 엔지니어링 Prompt Engineering: AI에게 원하는 결과를 얻기 위해 입력(프롬프트)을 효과적으

프롬프트 엔지니어링 Prompt Engineering프롬프트만 잘작성해도 원하는 결과를 얻을 수 있다. 프롬프트 엔지니어링(Prompt Engineering) AI에게 원하는 결과를 얻기 위해 입력(프롬프트)을 효과적으로 작성

standout.tistory.com

 

 

 

Playground

코드를 작성하지않고 웹화면에서 LLM API 동작을 테스트할 수 있는 실습도구

Playground 도 실제 API 호출과 같은 방식으로 동작하므로, 사용한 토큰은 API 사용량과 과금에 포함될 수 있다

 

https://platform.openai.com/login?next=%2Fchat%2Fedit%3Futm_source%3Dchatgpt.com

 

OpenAI Platform

 

platform.openai.com

 

 

 

 

API

사용자입력 - 서버/앱에서 API 요청생성 - LLM 서버가 응답생성 - 응답을 앱 화면에 출력

LLM API 과금은 보통 토큰 사용량 기준으로 모델별, 토큰 유형별로 가격이 다르다. 

비용절감 방법에는 질문과 관련없는 긴 문장을 제거, 출력 길이 제한, 요약본 사용, RAG 로 필요한 문단만 전달, 반복되는 시스템 프롬프트 최소화, 저렴한 모델과 고성능 모델을 구분해서 사용하거나 Batch API 사용을 검토한다 .

LLM을 실무에 적용할떄 가장 중요한것은 좋은 프롬프트, 정확한 외부 ㄷ이터 연결, 토큰과 비용관리.

https://standout.tistory.com/1075

 

API란?

API Application Programming Interface 프로그램에 요청을 전달하기위한 통로 혹은 방법

standout.tistory.com

 

 

 

FastAPI

Python으로 REST API와 웹 백엔드 서버를 매우 쉬고 빠르게 개발 할 수 있도록 만들어진 고성능 웹프레임워크

https://standout.tistory.com/1163

 

SOAP와 REST의 차이

SOAP import javax.jws.WebMethod; import javax.jws.WebService; import javax.jws.soap.SOAPBinding; import javax.jws.soap.SOAPBinding.Style; @WebService @SOAPBinding(style = Style.RPC) public class CalculatorService { @WebMethod public int add(int num1, int n

standout.tistory.com

https://standout.tistory.com/606

 

REST란?

REST란? Representational State Transfer의 약자 하나의 URI가 고유한 리소스를 처리하는 공통 방식 화면은 그대로 유지하면서 데이터만 전송 https://ko.wikipedia.org/wiki/REST REST - 위키백과, 우리 모두의 백과사

standout.tistory.com

https://standout.tistory.com/1076

 

REST와 REST API, 그리고 RESTful

REST Representational State Transfer HTTP 프로토콜을 기반으로 자원을 정의하고 상태를 주고받는 방식 https://standout.tistory.com/606 REST란? REST란? Representational State Transfer의 약자 하나의 URI가 고유한 리소스를

standout.tistory.com

https://standout.tistory.com/1078

 

REST API URI의 6가지 규칙

앞서 REST API란 REST한 인터페이스라고 알아봤다. 이 REST란 인터페이스를 작성할때의 규칙을 알아보자. https://standout.tistory.com/1076 REST와 REST API, 그리고 RESTful REST Representational State Transfer HTTP 프로토

standout.tistory.com

 

 

 

 

 

실습 프로젝트 fastapi_board_pjt 분석해보자. 

 

env

데이터베이스 정보 저장

# MySQL 접속 문자열입니다.
# 형식: mysql+pymysql://아이디:비밀번호@호스트:포트/DB명?charset=utf8mb4
DATABASE_URL=mysql+pymysql://student:student!@localhost:3306/fastapi_board_db?charset=utf8mb4

# JWT 토큰 서명에 사용할 비밀키입니다. 실제 프로젝트에서는 긴 임의 문자열로 변경하세요.
SECRET_KEY=change-this-secret-key-to-a-long-random-string

# JWT 서명 알고리즘입니다.
ALGORITHM=HS256

# 액세스 토큰 만료 시간입니다.
ACCESS_TOKEN_EXPIRE_MINUTES=60

 

 

 

 

database.py

import 

dotenv load_dotenv .env 파일에 저장된 환경변수를 파이썬 환경으로 불러온다. 

sqlalchempy.orm declarative_base, sessionmaker ORM 모델 기본 클래스와 DB 세션 팩토리를 만든다. 

load_dotenv() 루트의 env파일을 읽어 환경변수로 등록한다. 

database_url 도 os.getenv() 로 저장.  url이 없으면 alert

create_engine() sqlalchemy의 db연결 엔진을 생성하며 pool_pre_ping T는 Mysql연결이 끊어진경우 확인해 재연결

sessionmaker() 요청마다 사용할 db 섹션 객체 만들기. autocommit, autoflush를 F해 수동커밋방식 세션펙토리를 만든다.

sqlalchemy.orm의 declareative_base 객체 만들기. ORM 테이블 모델의 부모 클래스. base에 할당

get_db()시 sessionLocal() db 새션을 만들고 db를 전달 및 db.close() 자원을 반납한다.

# app/database.py
# FastAPI 앱이 MySQL 데이터베이스와 연결할 때 사용하는 공통 설정 파일입니다.

import os  # 운영체제 환경변수를 읽기 위해 사용하는 표준 라이브러리입니다.
from dotenv import load_dotenv  # .env 파일에 저장된 환경변수를 파이썬 환경으로 불러옵니다.
from sqlalchemy import create_engine  # SQLAlchemy가 DB 연결 엔진을 만들 때 사용합니다.
from sqlalchemy.orm import declarative_base, sessionmaker  # ORM 모델 기본 클래스와 DB 세션 팩토리를 만듭니다.

load_dotenv()  # 프로젝트 루트의 .env 파일을 읽어 환경변수로 등록합니다.

# DATABASE_URL은 .env 파일에서 읽어옵니다.
# 예: mysql+pymysql://root:1234@localhost:3306/fastapi_board_db?charset=utf8mb4
DATABASE_URL = os.getenv("DATABASE_URL")  # DB 접속 문자열을 환경변수에서 가져옵니다.

# DATABASE_URL이 없으면 실행 초기에 원인을 알 수 있도록 명확한 오류를 발생시킵니다.
if not DATABASE_URL:  # 환경변수가 비어 있는지 확인합니다.
    raise RuntimeError("DATABASE_URL 환경변수가 없습니다. .env 파일을 확인하세요.")  # 설정 누락 오류를 표시합니다.

# create_engine은 SQLAlchemy의 DB 연결 엔진을 생성합니다.
# pool_pre_ping=True는 오래된 MySQL 연결이 끊어진 경우 자동으로 확인하여 재연결 안정성을 높입니다.
engine = create_engine(DATABASE_URL, pool_pre_ping=True)  # MySQL 접속 엔진을 생성합니다.

# sessionmaker는 요청마다 사용할 DB 세션 객체를 만들어주는 공장 역할을 합니다.
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)  # 수동 commit 방식의 세션 팩토리를 만듭니다.

# Base는 SQLAlchemy ORM 모델 클래스들이 상속받는 기준 클래스입니다.
Base = declarative_base()  # ORM 테이블 모델의 부모 클래스를 생성합니다.


def get_db():
    # FastAPI 의존성 주입용 함수입니다.
    # 라우터 함수에서 Depends(get_db)로 사용하면 요청마다 DB 세션을 하나씩 받을 수 있습니다.
    db = SessionLocal()  # 새로운 DB 세션을 생성합니다.
    try:  # 요청 처리 중 예외가 발생해도 세션을 닫기 위해 try-finally를 사용합니다.
        yield db  # 라우터 함수에 DB 세션을 전달합니다.
    finally:  # 요청 처리가 끝난 뒤 항상 실행됩니다.
        db.close()  # DB 세션을 닫아 연결 자원을 반환합니다.

 

 

 

 

schemas.py

import datetime

pydantic의 basemodel, field. basemodel 는Pydantic모델의 부모클래스로 모든 요청과 응답 스키마는 basemodel을 상속받는다. field는 각 필드 조건을 지정하는 함수 . 데이터를 저장하고 검증하는 기능을 이미 가지고있는 클래스. 우리는 이것을 가져와 작성한다. 상속. BaseModel은 객체를 만들면 자동으로 타입검사, 필수값검사, json을 객체변환, 객체를 json변환을 모두 해준다 .

class UserCreate(BaseModel):
    username: str
    password: str

이러한 코드를 Pydantic이 

class UserCreate(BaseModel):
    username = Field(...)
    password = Field(...)
    def __init__(...):
        # 검증
        # 타입 변환
        # JSON 처리

등으로 코드를 자동생성해 별도의 생성자, 검증코드를 작성하지않아도 사용할 수 있는것 .

from datetime import datetime  # 응답에 날짜와 시간을 포함하기 위해 사용합니다.
from pydantic import BaseModel, Field  # 데이터 검증 모델과 필드 조건을 정의하기 위해 사용합니다.

 

 

 

 

UserCreate(BaseModel)

회원가입 요청 데이터 구조 정의.

usename, password, name

UserResponse(BaseModel)

회원가입후 반호나할 회원 정보 구조 정의

id, username, name, created_at, model_config

model_config = {"from_attributes": True} 

db에서 조회한다고 user 객체와 형이 맞지않는다. 응답은 Pydantic 모델이어야하니 fastapi는 return user를 했을때 자동으로 UserResponse()객체를 만들어야한다 . json처럼 사전형태를 읽는 것이 기본은 Pydantic과 달리 SQLAichemy는 속성으로 가지고 있음으로 user.id dict 말고 객체의 attribute에서도 값을 읽어보세요~ 하고 알려주는것. 

class UserCreate(BaseModel):
    # 회원가입 요청 데이터 구조입니다.
    username: str = Field(..., min_length=3, max_length=50, description="로그인 아이디")  # 아이디 길이를 검증합니다.
    password: str = Field(..., min_length=4, max_length=100, description="로그인 비밀번호")  # 비밀번호 길이를 검증합니다.
    name: str = Field(..., min_length=1, max_length=50, description="회원 이름")  # 이름 길이를 검증합니다.


class UserResponse(BaseModel):
    # 회원가입 성공 후 반환할 회원 정보 구조입니다.
    id: int  # 회원 고유 번호입니다.
    username: str  # 로그인 아이디입니다.
    name: str  # 회원 이름입니다.
    created_at: datetime  # 가입 시각입니다.

    model_config = {"from_attributes": True}  # SQLAlchemy ORM 객체를 Pydantic 응답으로 변환할 수 있게 합니다.

 

 

 

TOkenResonse()

access_token과 token_type을 bearer로 정의. 

class TokenResponse(BaseModel):
    # 로그인 성공 후 반환할 JWT 토큰 응답 구조입니다.
    access_token: str  # API 인증에 사용할 액세스 토큰입니다.
    token_type: str = "bearer"  # Swagger 인증 방식에서 사용하는 토큰 타입입니다.

 

 

Board CRU

BoardCreate() title 과 content구조

BoardUpdate() title 과 content

BoardListResponse() 게시글 번호 id, 조회수 view_count, 작성자이름 writer_name, 작성시간 created_at구조. 이때도 model_config = {"from_attrivutes":True} 설정

BoardDetailResponse() 마찬가지. writerid와 updated_ad 추가. 상세조회이니 데이터가 조금 더 추가되었다. 

class BoardCreate(BaseModel):
    # 게시글 등록 요청 데이터 구조입니다.
    title: str = Field(..., min_length=1, max_length=200, description="게시글 제목")  # 제목 필수 입력 조건입니다.
    content: str = Field(..., min_length=1, description="게시글 내용")  # 내용 필수 입력 조건입니다.


class BoardUpdate(BaseModel):
    # 게시글 수정 요청 데이터 구조입니다.
    title: str = Field(..., min_length=1, max_length=200, description="수정할 게시글 제목")  # 수정 제목입니다.
    content: str = Field(..., min_length=1, description="수정할 게시글 내용")  # 수정 내용입니다.


class BoardListResponse(BaseModel):
    # 게시글 전체 조회에서 사용할 간단 응답 구조입니다.
    id: int  # 게시글 번호입니다.
    title: str  # 게시글 제목입니다.
    view_count: int  # 게시글 조회수입니다.
    writer_name: str  # 작성자 이름입니다.
    created_at: datetime  # 작성 시각입니다.

    model_config = {"from_attributes": True}  # ORM 객체 기반 변환을 허용합니다.


class BoardDetailResponse(BaseModel):
    # 게시글 상세 조회에서 사용할 응답 구조입니다.
    id: int  # 게시글 번호입니다.
    title: str  # 게시글 제목입니다.
    content: str  # 게시글 내용입니다.
    view_count: int  # 게시글 조회수입니다.
    writer_id: int  # 작성자 회원 번호입니다.
    writer_name: str  # 작성자 이름입니다.
    created_at: datetime  # 작성 시각입니다.
    updated_at: datetime  # 수정 시각입니다.

    model_config = {"from_attributes": True}  # ORM 객체 기반 변환을 허용합니다.

 

 

 

 

 

 

security.py

import

dontenv load_dotenv .env 파일 불러오기

fastapi 의존성 depends, 응답 httpesception, status

fastapi.security OAuth2passwordBearer Bearer 토큰 인증을 처리하는 객체를 가져온다 . http 요청의 Authorization 헤더를 읽어 Bearer <JMT토큰> 형식인지 확인해서 함수에 전달한다. 

jose JWTError, jwt 토큰 생성과 해석에 사용되는 jwt와 토큰이 잘못될을때 발생하는 예외 import 토큰이 위조된경우,만료된경우 형식이 잘못된경우 등

passlib.context CryprContext 비밀번호를 안전하게 암호화하는 라이브러리

sqlalchemy.orm session SQLAlchemy의 데이터 베이스 세션 타입. 인증과정때 사용자를 조회해야함으로 사용한다. 데이터베이스에서 회원정보를 조회할때 사용한다. 

app.database 가져오기. model가져오기

import os  # .env에서 보안 설정값을 읽기 위해 사용합니다.
from datetime import datetime, timedelta, timezone  # JWT 만료 시간을 계산하기 위해 사용합니다.
from dotenv import load_dotenv  # .env 파일을 환경변수로 불러옵니다.
from fastapi import Depends, HTTPException, status  # 인증 실패 응답과 의존성 주입에 사용합니다.
from fastapi.security import OAuth2PasswordBearer  # Swagger Authorize 버튼과 Bearer 토큰 인증에 사용합니다.
from jose import JWTError, jwt  # JWT 토큰 인코딩과 디코딩에 사용합니다.
from passlib.context import CryptContext  # 안전한 비밀번호 해싱을 위해 사용합니다.
from sqlalchemy.orm import Session  # DB 세션 타입 힌트에 사용합니다.
from app.database import get_db  # DB 세션 의존성 함수입니다.
from app.models import User  # 현재 로그인 사용자를 조회하기 위해 User 모델을 가져옵니다.

 

 

env파일값을 가져오기.

secret_key jwt를 생성하고 검증할때 사용하는 비밀키. jwt.encode()를 사용해 jwt를 생성하는데 이때 반드시 비밀키가 필요하다. 

algorithm jwt 서명 알고리즘 선택. HS256 사용.  HMAC + SHA-256방식의 암호화 알고리즘으로 jwt에서 가장 많이 사용하는 알고리즘이다 .

assess_token_expire_ninutes 토큰 유효시간.설정.

CryptContext() Passlib 라이브러리에서 제공하는 비밀번호 암호화 관리자. 이이 객체를 사용하면 비밀번호가 암호화된다. 로그인할때는 pwd.context.verify()를 사용해 비밀번호가 맞는지 확인한다. schemes = ["bcrypt"] 암호화알고리즘으로 bcrypt를 사용하겠다는 의미이고 bcrypt는 매우 안전해 비밀버니호 저장에 가장 많이 사용하는 알고리즘 중 하나. deprecated auto는 나중에 더 좋은 알고리즘으로 변경시 자동으로 처리하도록 하는 옵션이다. 

OAuth2PasswordBerarer() 토큰을 읽어오는 객체를 생성해 토큰을 발급받은 api 주소를 정의한다. swagger에서는 이 정보를 이용해서 Authorize 버튼을 만들고 로그인을 수행하며 로그인 성공후 발급바디은 jwt를 자동으로 저장한다. 

load_dotenv()  # .env 파일 값을 환경변수로 등록합니다.

SECRET_KEY = os.getenv("SECRET_KEY", "change-this-secret-key")  # JWT 서명에 사용할 비밀키입니다.
ALGORITHM = os.getenv("ALGORITHM", "HS256")  # JWT 서명 알고리즘입니다.
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))  # 토큰 만료 시간입니다.

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")  # bcrypt 방식으로 비밀번호를 해싱합니다.
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")  # Swagger에서 로그인 API를 토큰 발급 URL로 인식하게 합니다.

 

 

 

hash_password() 

사용자 비밀번호를 해시 문자열로 변환해 return. 

password도 

def hash_password(password: str) -> str:
    # 사용자가 입력한 원문 비밀번호를 DB 저장용 해시 문자열로 변환합니다.
    return pwd_context.hash(password)  # bcrypt 해시 결과를 반환합니다.


def verify_password(plain_password: str, hashed_password: str) -> bool:
    # 로그인 시 입력한 원문 비밀번호와 DB에 저장된 해시 비밀번호가 일치하는지 검증합니다.
    return pwd_context.verify(plain_password, hashed_password)  # 일치하면 True, 아니면 False를 반환합니다.

 

 

 

create_access_token()

data를 copy()해서 expire 토큰 만료시각을 계산한다. datetime.now(timezone.utc) 현재 시간을 가져와 timedelta 시간을 더하거나 빼는 객체로 계산해 to_encode에 업데이트.

위를 jwt() 에 넣는데 비밀키와 서명알고리즈도 함께하여 return.

def create_access_token(data: dict) -> str:
    # JWT 액세스 토큰을 생성합니다.
    to_encode = data.copy()  # 원본 데이터가 변경되지 않도록 복사합니다.
    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)  # 만료 시각을 계산합니다.
    to_encode.update({"exp": expire})  # JWT payload에 만료 시각을 추가합니다.
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)  # payload를 서명하여 JWT 문자열로 변환합니다.
    return encoded_jwt  # 완성된 JWT 토큰을 반환합니다.

 

 

get_current_user()

tikens는 Depends(oauth2_scheme) 요청헤더에서 JWT를 자동으로 꺼내서 token 변수에 넣어준다. 또 db_session도 depends(get_db) 에서 가져온다. 

인증실채시 httpexception() 예외 객체를 생성한다. 

jwt.decode() 디코드해 username 사용자 이름을 꺼내고 없으면 오류발생.

토큰 username으로 db에서 조회해 마찬가지로 없을경우 오류발생. 

인증된 사용자를 return.

def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)) -> User:
    # 보호된 API에서 현재 로그인 사용자를 확인하는 의존성 함수입니다.
    credentials_exception = HTTPException(  # 토큰 검증 실패 시 반환할 401 오류 객체입니다.
        status_code=status.HTTP_401_UNAUTHORIZED,  # 인증 실패 HTTP 상태 코드입니다.
        detail="인증 정보가 올바르지 않습니다.",  # 클라이언트에 전달할 오류 메시지입니다.
        headers={"WWW-Authenticate": "Bearer"},  # Bearer 인증 실패임을 나타냅니다.
    )
    try:  # JWT 디코딩 과정에서 오류가 날 수 있으므로 try 문을 사용합니다.
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])  # 토큰을 검증하고 payload를 추출합니다.
        username: str | None = payload.get("sub")  # payload에서 사용자 아이디를 꺼냅니다.
        if username is None:  # 사용자 아이디가 없으면 잘못된 토큰입니다.
            raise credentials_exception  # 401 오류를 발생시킵니다.
    except JWTError:  # 토큰 위조, 만료, 형식 오류가 발생한 경우입니다.
        raise credentials_exception  # 401 오류를 발생시킵니다.

    user = db.query(User).filter(User.username == username).first()  # 토큰의 username으로 DB에서 회원을 조회합니다.
    if user is None:  # DB에 해당 사용자가 없으면 인증 실패입니다.
        raise credentials_exception  # 401 오류를 발생시킵니다.
    return user  # 인증된 현재 사용자 객체를 반환합니다.

 

 

 

auth.py

import

fastapi에서 APIRouter, Depends, HTTPException, status를 가져온다.  APIRouter는 API를 그룹별로 관리하기 위한 객체로 /auth/login, /auth/signup처럼 인증 관련 API를 하나의 그룹으로 묶어 관리할 수 있다. depends는 의존성 주입을 위한 기능으로 

db: Session = Depends(get_db) 라고 작성할시 FastAPI가 자동으로 db=getdb()를 실행해 데이터베이스 세션을 전달해 매번 db연결코드를 작성하지않아도된다. HTTPException은 예외가 발생해씨을때 http 오류를 반환한다.

raise HTTPException(
    status_code=400,
    detail="이미 사용 중인 아이디입니다."

status는 http상태코드를 쉽게 사용할 수 있도록 제공한다. status.HTTP_201_CREATED 숫자를 직접 쓰는 것보다 의미를 쉽게 알 수 있다 .

fastapi.security에서 OAuth2PasswordRequestForm 로그인 폼 데이터를 입력 받기 위해 사용한다. swagger에서는 username, password 두값을 x-www-form-urlencoded형식으로 자동전송해준다.

sqlalchemy.orm  session 데이터조회, 추가, 삭제, 수정을 담당하며 이들이 모두 session을 이용해 작업된다.

app.database에서 get_db를 가져와 db세션을 받는다.  db연결을 생성하는 함수.

app.models 에서 users 테이블 모델을 가져온다.  

app.schemas에서 TokenResponse, UseCreate, UseResponse를 가져온다.  입력과 출력을 검증한다. 

app.security에서 create_access_token, hash_password, verify_password 를 가져온다. 비밀번호를 암호화하고 로그인시 입력한 비밀번호와 db의 암호화된 비밀번호를 비교하는 것. 

# app/routers/auth.py
# 회원가입과 로그인 API를 정의하는 라우터 파일입니다.

from fastapi import APIRouter, Depends, HTTPException, status  # 라우터, 의존성 주입, 오류 응답을 사용합니다.
from fastapi.security import OAuth2PasswordRequestForm  # Swagger 로그인 폼 데이터를 받기 위해 사용합니다.
from sqlalchemy.orm import Session  # DB 세션 타입 힌트에 사용합니다.
from app.database import get_db  # DB 세션을 주입받기 위해 사용합니다.
from app.models import User  # users 테이블 ORM 모델입니다.
from app.schemas import TokenResponse, UserCreate, UserResponse  # 요청/응답 스키마입니다.
from app.security import create_access_token, hash_password, verify_password  # 보안 유틸 함수입니다.

 

 

router에 APIRouter 그룹을 만든다. 모든 api앞에 /auth등 이 자동으로 붙게한다. prefix='/auth'등을 지정해 인증관련 api를 그룹화할 수 있다 .

router = APIRouter(prefix="/auth", tags=["회원 인증"] )  # /auth로 시작하는 인증 관련 API 그룹을 만듭니다.

 

 

회원가입 sign up

@router.post() 회원가입 API 등록.

response_model=UserResponse 응답데이터를 UserResponse 스키마 형태로 반환. password_hash와같은 민감정보는 응답에 포함되징낳는다. 

status_code =  status.HTTP_201_created 회원가입 성공시 http상태코드 201을 반환함.

db.query() 동일 아이디가 있는지 확인. 안내.

User() 객체 생성. db세션에 추가 . commit() refresh() 자동생성된 id과 cteate_at 값을 객체에 반영

return

@router.post("/signup", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def signup(user_data: UserCreate, db: Session = Depends(get_db)):
    # 간단 회원가입 API입니다.
    existing_user = db.query(User).filter(User.username == user_data.username).first()  # 동일 아이디가 있는지 조회합니다.
    if existing_user:  # 이미 존재하는 아이디라면 가입을 막습니다.
        raise HTTPException(status_code=400, detail="이미 사용 중인 아이디입니다.")  # 400 오류를 반환합니다.

    new_user = User(  # DB에 저장할 새 User ORM 객체를 생성합니다.
        username=user_data.username,  # 요청에서 받은 아이디를 저장합니다.
        password_hash=hash_password(user_data.password),  # 원문 비밀번호를 해시하여 저장합니다.
        name=user_data.name,  # 요청에서 받은 이름을 저장합니다.
    )
    db.add(new_user)  # 새 회원 객체를 DB 세션에 추가합니다.
    db.commit()  # INSERT 쿼리를 실제 DB에 반영합니다.
    db.refresh(new_user)  # DB에서 자동 생성된 id와 created_at 값을 객체에 반영합니다.
    return new_user  # 가입된 회원 정보를 반환합니다.

 

 

 

 

로그인 login

response_model=TokenResponse Token Response 스키마 형태로 반환한다. 이는 schemas.py에 정의되어있다. 

입력받은 id가 회원에 있는지 확인. 안내. 비밀번호확인 

create_access_token() username을 넣어 access token 생성 tokenresponse형태로 return.

 token_type="bearer" 발급된 토큰 인증방식을 알려줌  Bearer인증방식을 사용한다는 의미.

http에서는 인증방식이 여러가지가 있는데 basic은 아이디와 비밀번호, bearer는 토큰을 이용하는 방식이다. 

서버는 앞의 단어를 보고 이 요청을 Bearer토큰으로 인증하는구나를 알 수 있다 .

@router.post("/login", response_model=TokenResponse)
def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
    # 간단 로그인 API입니다.
    # Swagger Authorize와 호환되도록 JSON이 아니라 x-www-form-urlencoded 형식의 username/password를 받습니다.
    user = db.query(User).filter(User.username == form_data.username).first()  # 아이디로 회원을 조회합니다.
    if not user:  # 회원이 없으면 로그인 실패입니다.
        raise HTTPException(status_code=401, detail="아이디 또는 비밀번호가 올바르지 않습니다.")  # 401 오류를 반환합니다.

    if not verify_password(form_data.password, user.password_hash):  # 입력 비밀번호와 저장된 해시를 비교합니다.
        raise HTTPException(status_code=401, detail="아이디 또는 비밀번호가 올바르지 않습니다.")  # 401 오류를 반환합니다.

    access_token = create_access_token(data={"sub": user.username})  # username을 subject로 넣어 JWT를 생성합니다.
    return TokenResponse(acc

 

 

 

 

 

main.py

import

fastapi Fastapi 객체를 만듬.

작성한 database와 routers를 받음

Base.metadata.create_all() ORM 모델 기준으로 테이블 자동생성.

app FastAPI() 객체 생성. title과 description, version 지정. 

app.include_router() auth정보와 boards경로의 router를 앱에 등록한다. 

/이면 root() 안내문구 출력.

# app/main.py
# FastAPI 앱의 시작점입니다.

from fastapi import FastAPI  # FastAPI 애플리케이션 객체를 만들기 위해 사용합니다.
from app.database import Base, engine  # 테이블 생성에 필요한 Base와 DB 엔진입니다.
from app.routers import auth, boards  # 회원 인증 라우터와 게시판 라우터를 가져옵니다.

# 개발/실습 편의를 위해 앱 시작 시 ORM 모델 기준으로 테이블을 자동 생성합니다.
# 운영 환경에서는 Alembic 같은 마이그레이션 도구 사용을 권장합니다.
Base.metadata.create_all(bind=engine)  # users, boards 테이블이 없으면 자동 생성합니다.

app = FastAPI(  # FastAPI 앱 객체를 생성합니다.
    title="FastAPI MySQL 자유게시판 CRUD API",  # Swagger 문서 상단 제목입니다.
    description="간단 회원가입, 로그인, 자유게시글 CRUD, 조회수 증가 기능을 제공하는 백엔드 예제입니다.",  # Swagger 설명입니다.
    version="1.0.0",  # API 버전입니다.
)

app.include_router(auth.router)  # /auth 경로의 회원 인증 API를 앱에 등록합니다.
app.include_router(boards.router)  # /boards 경로의 게시판 API를 앱에 등록합니다.


@app.get("/")
def root():
    # 서버 실행 확인용 기본 API입니다.
    return {"message": "FastAPI MySQL 자유게시판 API가 실행 중입니다. /docs에서 Swagger를 확인하세요."}  # 상태 메시지를 반환합니다.

 

 

 

models.py

import 

datetime가져오기

sqlalchemy 데이터 컬럼 정의

sqlalchemy.orm relationship 관계를 표현하기위해 사용

정의한 app.database Base 클래스 가져오기

from datetime import datetime  # 게시글 작성일과 수정일의 기본값을 만들기 위해 사용합니다.
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text  # 테이블 컬럼 타입을 정의하기 위해 사용합니다.
from sqlalchemy.orm import relationship  # User와 Board 사이의 관계를 표현하기 위해 사용합니다.
from app.database import Base  # 모든 ORM 모델이 상속받는 Base 클래스를 가져옵니다.

 

 

 

User() database의 Base클래스 매개변수. 

user 테이블 이름 지정. 

id, username, password_hash, name, create)at 정의. 

post relationship() 회원이 작성한 board, 와 user는 서로 연결되어있다는 것을 알려준다 . cascade="all" 부모에게 발생한 자기업을 board에서도 적용해 user가 삭제되면 user가 작성한 board도 삭제되는것이며  delete-orphan 속성은 만일 회원을 삭제했는데 게시글이 그대로 남아있을경우 즉 고아데이터 Orphan data를 방지하기위해 사용한다. 

Board() 반대로 

마찬가지로 테이블, 아티틀, content, view_count, user_id, create_at, update_at 정의하고 writer로 user를 정의한다. 

class User(Base):
    # users 테이블과 매핑되는 회원 모델입니다.
    __tablename__ = "users"  # 실제 MySQL 테이블 이름을 지정합니다.

    id = Column(Integer, primary_key=True, index=True)  # 회원 고유 번호이며 기본키입니다.
    username = Column(String(50), unique=True, index=True, nullable=False)  # 로그인 아이디이며 중복을 허용하지 않습니다.
    password_hash = Column(String(255), nullable=False)  # 원문 비밀번호가 아니라 해시된 비밀번호를 저장합니다.
    name = Column(String(50), nullable=False)  # 회원 이름을 저장합니다.
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)  # 회원 가입 시각을 UTC 기준으로 저장합니다.

    posts = relationship("Board", back_populates="writer", cascade="all, delete-orphan")  # 회원이 작성한 게시글 목록 관계입니다.


class Board(Base):
    # boards 테이블과 매핑되는 자유게시글 모델입니다.
    __tablename__ = "boards"  # 실제 MySQL 테이블 이름을 지정합니다.

    id = Column(Integer, primary_key=True, index=True)  # 게시글 고유 번호이며 기본키입니다.
    title = Column(String(200), nullable=False)  # 게시글 제목을 저장합니다.
    content = Column(Text, nullable=False)  # 게시글 본문을 저장합니다.
    view_count = Column(Integer, default=0, nullable=False)  # 상세 조회 시 1씩 증가하는 조회수입니다.
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)  # 작성자 users.id를 참조하는 외래키입니다.
    created_at = Column(DateTime, default=datetime.utcnow, nullable=False)  # 게시글 작성 시각입니다.
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)  # 수정 시 자동 갱신되는 시각입니다.

    writer = relationship("User", back_populates="posts")  # 게시글 작성자 정보를 가져오기 위한 관계입니다.

 

 

 

 

 

boards.py

import

fastapi APiRounter, Depens, HTTPException, status 라우터, 의존성주입, http오류를 사용한다. 

sqlalchemy.orm session 세션 불러오기 

app에서 작성한 database, model, schemas, security모두 불러오기

라우터 설정. /boards로 시작하는 게시판 그룹

from fastapi import APIRouter, Depends, HTTPException, status  # 라우터, 의존성 주입, HTTP 오류를 사용합니다.
from sqlalchemy.orm import Session  # DB 세션 타입 힌트에 사용합니다.
from app.database import get_db  # DB 세션 의존성 함수입니다.
from app.models import Board, User  # 게시글과 회원 ORM 모델입니다.
from app.schemas import BoardCreate, BoardDetailResponse, BoardListResponse, BoardUpdate  # 게시글 요청/응답 스키마입니다.
from app.security import get_current_user  # 로그인 사용자 확인 의존성 함수입니다.

router = APIRouter(prefix="/boards", tags=["자유게시판"] )  # /boards로 시작하는 게시판 API 그룹을 만듭니다.

 

 

to_board_detail_response()

boarddetailresponse() 형 board orm 객체를 return.

to_board_list_response() 

boradlistresponse() 형 객체를 return

def to_board_detail_response(board: Board) -> BoardDetailResponse:
    # Board ORM 객체를 상세 응답 스키마로 변환하는 보조 함수입니다.
    return BoardDetailResponse(  # Pydantic 응답 객체를 생성합니다.
        id=board.id,  # 게시글 번호입니다.
        title=board.title,  # 게시글 제목입니다.
        content=board.content,  # 게시글 내용입니다.
        view_count=board.view_count,  # 게시글 조회수입니다.
        writer_id=board.writer.id,  # 작성자 번호입니다.
        writer_name=board.writer.name,  # 작성자 이름입니다.
        created_at=board.created_at,  # 작성 시각입니다.
        updated_at=board.updated_at,  # 수정 시각입니다.
    )


def to_board_list_response(board: Board) -> BoardListResponse:
    # Board ORM 객체를 목록 응답 스키마로 변환하는 보조 함수입니다.
    return BoardListResponse(  # Pydantic 목록 응답 객체를 생성합니다.
        id=board.id,  # 게시글 번호입니다.
        title=board.title,  # 게시글 제목입니다.
        view_count=board.view_count,  # 게시글 조회수입니다.
        writer_name=board.writer.name,  # 작성자 이름입니다.
        created_at=board.created_at,  # 작성 시각입니다.
    )

 

 

 

@router.post board 다음에 url에 아무것도 없을때 response_model은 BoardDetailresponse.일때 성공시 201.

create_board() 

board 데이터를 받아 session을 저장하고, current_user를 수행해 성공하면 사용자 orm 객체를 받는다. 

board 객체 title, content, user_id를 받아 db에 add commit refresh. return

@router.post("", response_model=BoardDetailResponse, status_code=status.HTTP_201_CREATED)
def create_board(board_data: BoardCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    # 로그인한 사용자만 게시글을 등록할 수 있는 API입니다.
    new_board = Board(  # DB에 저장할 새 게시글 ORM 객체를 생성합니다.
        title=board_data.title,  # 요청 제목을 저장합니다.
        content=board_data.content,  # 요청 내용을 저장합니다.
        user_id=current_user.id,  # 현재 로그인 사용자를 작성자로 저장합니다.
    )
    db.add(new_board)  # 새 게시글 객체를 DB 세션에 추가합니다.
    db.commit()  # INSERT 쿼리를 실제 DB에 반영합니다.
    db.refresh(new_board)  # DB에서 생성된 id와 시각 정보를 객체에 반영합니다.
    return to_board_detail_response(new_board)  # 생성된 게시글 상세 정보를 반환합니다.

 

 

 

조회하기

@router.get, response_nodel이 board_listresponse일때

get_boards() db query에서 orderby() .all 전체최신순으로 조회해서 가져와 각 게시글을 응답구조로 변환해 return.

@router.get("", response_model=list[BoardListResponse])
def get_boards(db: Session = Depends(get_db)):
    # 게시글 전체 목록을 조회하는 API입니다.
    boards = db.query(Board).order_by(Board.id.desc()).all()  # 게시글을 최신순으로 전체 조회합니다.
    return [to_board_list_response(board) for board in boards]  # 각 게시글을 목록 응답 구조로 변환해 반환합니다.

 

 

 

상세조회하기

@router, /board_id id가 명시되어있고 response_model이 BoardDetailResponse일때

db query 에서 filter id값으로 필터링해서 조회해 가져온다. 이때 게시글이 없으면 에러를 반환하고 성공시 view_count를 +1 증가해 commit() refresh, return 상세응답 반환

@router.get("/{board_id}", response_model=BoardDetailResponse)
def get_board(board_id: int, db: Session = Depends(get_db)):
    # 게시글 상세 조회 API입니다.
    # 이 API는 조회할 때마다 조회수를 1 증가시킵니다.
    board = db.query(Board).filter(Board.id == board_id).first()  # 게시글 번호로 게시글을 조회합니다.
    if not board:  # 게시글이 없으면 404 오류를 반환합니다.
        raise HTTPException(status_code=404, detail="게시글을 찾을 수 없습니다.")  # 존재하지 않는 게시글 오류입니다.

    board.view_count += 1  # 상세 조회 성공 시 조회수를 1 증가시킵니다.
    db.commit()  # UPDATE 쿼리를 실제 DB에 반영합니다.
    db.refresh(board)  # 증가된 조회수 값을 객체에 다시 반영합니다.
    return to_board_detail_response(board)  # 상세 응답을 반환합니다.

 

 

수정하기

@router.put() board_id id가 명시되어있고 repsonse_model이 BoardDetailResponse일때

update_board() 

db 에서 id로 filter를 걸어 첫번째 게시글을 가져오되 게시글이 없으면 에러, id가 달라도 에러. 

title, content를 가져다가 commit refresh,

return 상세정보 반환.

@router.put("/{board_id}", response_model=BoardDetailResponse)
def update_board(board_id: int, board_data: BoardUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    # 로그인한 작성자만 게시글을 수정할 수 있는 API입니다.
    board = db.query(Board).filter(Board.id == board_id).first()  # 수정 대상 게시글을 조회합니다.
    if not board:  # 게시글이 없으면 404 오류를 반환합니다.
        raise HTTPException(status_code=404, detail="게시글을 찾을 수 없습니다.")  # 존재하지 않는 게시글 오류입니다.

    if board.user_id != current_user.id:  # 현재 로그인 사용자가 작성자인지 확인합니다.
        raise HTTPException(status_code=403, detail="본인이 작성한 게시글만 수정할 수 있습니다.")  # 권한 없음 오류입니다.

    board.title = board_data.title  # 제목을 새 값으로 변경합니다.
    board.content = board_data.content  # 내용을 새 값으로 변경합니다.
    db.commit()  # UPDATE 쿼리를 실제 DB에 반영합니다.
    db.refresh(board)  # 수정된 값을 객체에 다시 반영합니다.
    return to_board_detail_response(board)  # 수정된 게시글 상세 정보를 반환합니다.

 

 

 

 

삭제하기

@router.delete() board_id 가 명시되어있고 status 완료되었을때 삭제성공 204 반환

delete_board()

db에서 id값으로 가져온 게시글.

만일 게시글이 없으면 에러. 

id가 일치하지않아도 에러. 

db.delete(), commit() return.. delete는 리턴값이 없다. 

@router.delete("/{board_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_board(board_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
    # 로그인한 작성자만 게시글을 삭제할 수 있는 API입니다.
    board = db.query(Board).filter(Board.id == board_id).first()  # 삭제 대상 게시글을 조회합니다.
    if not board:  # 게시글이 없으면 404 오류를 반환합니다.
        raise HTTPException(status_code=404, detail="게시글을 찾을 수 없습니다.")  # 존재하지 않는 게시글 오류입니다.

    if board.user_id != current_user.id:  # 현재 로그인 사용자가 작성자인지 확인합니다.
        raise HTTPException(status_code=403, detail="본인이 작성한 게시글만 삭제할 수 있습니다.")  # 권한 없음 오류입니다.

    db.delete(board)  # 게시글 삭제 SQL을 준비합니다.
    db.commit()  # DELETE 쿼리를 실제 DB에 반영합니다.
    return None  # 204 응답은 본문을 반환하지 않습니다.

 

 

 

FastAPI만으로도 UI처럼 보이는 화면이 자동으로 생긴다 .UI가 만들어진다기보다는 API문서를 자동생성해서 UI처럼 보여주는 것이지만

FastAPI를 실행하면 기본적으로 Swagger UI가 자동생성되고 ReDoc도 자동생성된다.

stramlit같은 프론트엔드 앱은 아니지만 API 테스트용 웹이라 볼 수 있다.

 

 

 

테스트

signup

 

 

 

 

 

login

 

 

 

 

 

로그인

 

 

 

 

로그인을 안하면 에러남

 

 

 

boards

 

 

 

 

boards/board_is

 

 

 

 

수정

 

 

 

 

삭제 및 삭제확인

 

 

 

기본확인

 

 

 

스키마 확인

 

 

 

api 키를 발급받아보자 .

api는 프로젝트별로 하나씩 발급이 된다. 

https://platform.openai.com/settings/proj_4jECqllwUnuxaipvAI4D77QZ