[Monitoring System] 6. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (2) - MQTT ๋ฐ์ดํ„ฐ ์ ์žฌ / ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„

2023. 8. 7. 14:48ยท๐ŸŽผ Project/๐ŸงŠ Monitoring System
728x90

2023.06.13 - [Project/Monitoring System] - [Monitoring System] 5. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (1)

 

[Monitoring System] 5. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (1)

from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, TIMESTAMP, Float from .base import Base ์ œ๋ชฉ์€ ๊ฑฐ์ฐฝํ•˜๊ฒŒ ์จ๋†จ์ง€๋งŒ ์•„์ฃผ ๊ฐ„๋‹จํ•œ ๋ฐฑ์—”๋“œ๋ฅผ ๊ตฌ์ถ•ํ•ด์„œ ๊ทธ๋ฆฌ ์˜ค๋ž˜๊ฑธ๋ฆฌ์ง€ ์•Š์•˜์„ ๋ฟ์ด๋‹ค^^. ์šฐ์„  ๋ฐฑ์—”๋“œ๋ฅผ

dnai-deny.tistory.com

์ „์ฒด ์ฝ”๋“œ

https://github.com/melli0505/monitoring_system

0. ๋ณ€๊ฒฝ์‚ฌํ•ญ

1ํŽธ์„ ์˜ฌ๋ฆฌ๊ณ  ๋ฌด๋ ค 2๋‹ฌ์˜ ์‹œ๊ฐ„์ด ์ง€๋‚ฌ๋‹ค. ๋•Œ๋ฌธ์— ๋‚ด ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์—๋„ ๋งŽ์€ ๋ณ€ํ™”๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ... ์‚ฌ์‹ค ๊ทธ๋ ‡๊ฒŒ ๋งŽ์€ ๋ณ€ํ™”๋Š” ์•„๋‹ˆ๋‹ค(?). 1ํŽธ์—์„œ๋Š” ํ…Œ์ด๋ธ”์ด user, data, sensor ์ด๋ ‡๊ฒŒ ์„ธ ๊ฐ€์ง€๋กœ ๊ตฌ์„ฑํ•ด๋†จ์—ˆ๋Š”๋ฐ, ์ง€๊ธˆ์€ ๋”๋ฏธ๋ฐ์ดํ„ฐ๊ฐ€ ์•„๋‹Œ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด์„œ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•ด๋’€๊ธฐ ๋•Œ๋ฌธ์— sensor ์—†์ด data ์˜ ํƒ€์ž…์— ๋”ฐ๋ผ user ํฌํ•จ ์ด 7๊ฐœ์˜ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜์—ˆ๋‹ค.

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, index=True)
    email = Column(String, unique=True, index=True)
    hashed_password = Column(String, index=True)
    disable = Column(Boolean, index=True, default=True)

class Voltage(Base):
    __tablename__ = "voltages"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    voltage = Column(Float, index=True)

class Current(Base):
    __tablename__ = "currents"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    current = Column(Float, index=True)

class Power(Base):
    __tablename__ = "powers"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    power = Column(Float, index=True)

class Energy(Base):
    __tablename__ = "energies"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    energy = Column(Float, index=True)

class PF(Base):
    __tablename__ = "pfs"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    pf = Column(Float, index=True)

class Frequency(Base):
    __tablename__ = "frequencies"

    id = Column(Integer, primary_key=True, index = True)
    time = Column(TIMESTAMP, index=True)
    frequency = Column(Float, index=True)

๋ฐ์ดํ„ฐ์˜ ์ถœ์ฒ˜๋Š” ์ด ๋…€์„์ด๋‹ค. ์ด ๋…€์„๊ณผ ESP32 ๊ณ„์—ด ์•„๋‘์ด๋…ธ๋ฅผ ์—ฐ๊ฒฐํ•ด์„œ MQTT ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด๋„๋ก ํ•ด๋‘์—ˆ๋‹ค.

pzem 004t - ์ค‘๊ฐ„๊ด‘๊ณ  ์•„๋‹™๋‹ˆ๋‹ค

pzem-004t ์ œ์–ด๋ฅผ ์œ„ํ•œ ์•„๋‘์ด๋…ธ ์ฝ”๋“œ๋Š” ์—ฌ๊ธฐ์— ๊ตฌํ˜„ํ•ด๋‘์—ˆ๋‹ค. ๋‹น์žฅ ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ˆ ๋„˜์–ด๊ฐ€๋„๋ก ํ•˜์ž.

https://github.com/melli0505/ESP32_PZEM-004t

 

GitHub - melli0505/ESP32_PZEM-004t

Contribute to melli0505/ESP32_PZEM-004t development by creating an account on GitHub.

github.com

 

1. MQTT ๋ฐ์ดํ„ฐ ์ ์žฌ

์šฐ์„  MQTT๋กœ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋“ค์„ ๊ฐ ๋ฐ์ดํ„ฐ ํ˜•์‹์— ๋งž๊ฒŒ ํ˜ธํ™˜์„ ์‹œ์ผœ์ฃผ๊ณ , ์ ์ ˆํ•˜๊ฒŒ ์—…๋ฐ์ดํŠธ๋ฅผ ์‹œ์ผœ์ฃผ์–ด์•ผํ•œ๋‹ค. ๊ฑฐ๋‘์ ˆ๋ฏธํ•˜๊ณ  ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

import paho.mqtt.client as mqtt
import time
import json

from db import models
from db.base import SessionLocal

broker_address = "YOUR IP ADDRESS"

# subscriber callback

def on_message(client, userdata, message):
    db = SessionLocal()
    converted = str(message.payload.decode("utf-8"))
    value = json.loads(converted)
    print(message.topic, value['voltage'], type(value['voltage']))
    timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
    data = [models.Voltage(time=timestamp, frequency=value["voltage"]),
            models.Energy(time=timestamp, frequency=value["energy"]),
            models.Power(time=timestamp, frequency=value["power"]),
            models.Current(time=timestamp, frequency=value["current"]),
            models.Frequency(time=timestamp, frequency=value["frequency"]),
            models.PF(time=timestamp, frequency=value["pf"])]
    for db_data in data:
        db.add(db_data)
        db.commit()
        db.refresh(db_data)
    db.close()


if __name__ == "__main__":
    client = mqtt.Client("client")

    client.connect(broker_address, 1883)
    print("client connected")

    client.on_message = on_message
    client.subscribe("data")

    print("client loop started")
    client.loop_forever()

models ์—์„œ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘” ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์— ๋งž์ถฐ์„œ ๋“ค์–ด์˜จ ๋ฐ์ดํ„ฐ๋“ค์„ ๊ฐ€๊ณตํ•ด์ฃผ์—ˆ๋‹ค. ๋‚˜ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋Š” ์ด 6๊ฐœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋Š” ์ชฝ์—์„œ Json string ํ˜•ํƒœ๋กœ ๊ฐ€๊ณตํ•ด์„œ ๋ฐ›์•„์™”๊ธฐ ๋•Œ๋ฌธ์— ์ฒ˜๋ฆฌ๋ฅผ ์ด๋ ‡๊ฒŒ ํ•ด์ฃผ์—ˆ๋Š”๋ฐ, ๊ฐ์ž ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ๋งž๊ฒŒ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

db.add(db_data) ์™€ db.refresh() ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฌ๋ฆด ๋•Œ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค. ์‚ญ์ œ์˜ ๊ฒฝ์šฐ์—๋Š” delete -> commit ์ˆœ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ์ž˜ ์˜ฌ๋ผ๊ฐ”๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

๊ตฟ! ์ž˜ ๋“ค์–ด์˜จ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ด 6๊ฐœ์˜ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ” ๋ชจ๋‘ ์ž˜ ์ ์žฌ๋˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค์Œ์œผ๋กœ ํ•ด์ค„ ๊ฒƒ์€, ๊ทธ๋ ‡๋‹ค. ์„œ๋น„์Šค ๊ตฌํ˜„์ด๋‹ค.

 

2. ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„

๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์ฒ˜์Œ ๊ตฌํ˜„ํ•  ๋•Œ ๋งŽ์ด ํ—ค๋งธ๋‹ค. ์–ด๋–ป๊ฒŒ ํ˜„์žฌ ์ ‘์†ํ•œ ์‚ฌ๋žŒ์ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ๋žŒ์ธ์ง€, ๋ˆ„๊ตฌ์ธ์ง€๋ฅผ ์•Œ ์ˆ˜ ์žˆ์ง€? ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™”ํ•  ์ˆ˜ ์žˆ์ง€? ์ด๋Ÿฐ ์งˆ๋ฌธ๋“ค ๋์— ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ• ํ•˜๋‚˜๋ฅผ ์ ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ์ด๋Ÿฐ ์‹์œผ๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ์•„์ฃผ ์ปค๋‹ค๋ž€ ๋ณด์•ˆ ๋ฌธ์ œ๊ฐ€ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ฃผ์˜(...) 

์šฐ์„  ํ˜„์žฌ ์ ‘์†ํ•œ ์‚ฌ๋žŒ์˜ ๊ถŒํ•œ์€ ์ฟ ํ‚ค์— ์ €์žฅ๋œ๋‹ค. ์ฟ ํ‚ค์— access token์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ access token์„ ๋ฐœํ–‰ํ•ด์ฃผ๋ฉด, ์ด๊ฑธ ๊ฐ€์ง€๊ณ  ํŽ˜์ด์ง€๋ฅผ ์˜ฎ๊ฒจ๋‹ค๋‹ˆ๋ฉด์„œ ๋งŒ๋ฃŒ ์ „๊นŒ์ง€ ๋‚ด๊ฐ€ ๋ˆ„๊ตฌ์˜ˆ์š”! ํ•˜๊ณ  ์„œ๋ฒ„์— ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋‹ค. 

๊ทธ๋ฆฌ๊ณ  ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์•”ํ˜ธํ™”ํ•ด์„œ database์— ์ €์žฅ๋˜์–ด์•ผํ•˜๊ณ , ๋กœ๊ทธ์ธํ•  ๋•Œ ์•”ํ˜ธํ™” ๋ฐฉ์‹์— ๋”ฐ๋ผ ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋งค์นญ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ , ๋งž๋‹ค๋ฉด ํ† ํฐ์„ ๋ฐœํ–‰ํ•˜๋ฉด ๋œ๋‹ค. ํ•˜๋‚˜์”ฉ ์ž์„ธํžˆ ๋ณด์ž. 

 

A. Hashed password ์ƒ์„ฑํ•˜๊ธฐ

์šฐ์„  ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ถ€ํ„ฐ ํ•ด๋ณด์ž. passlib์˜ context์—์„œ CryptContext๋ผ๋Š” ๋ชจ๋“ˆ์„ ํ™œ์šฉํ•ด์„œ ์ƒ์„ฑํ•  ๊ฒƒ์ด๋‹ค. 

from passlib.context import CryptContext

password_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

bcrypt๋ผ๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ํ™œ์šฉํ•ด์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ฅผ ํ•ด์ค„ ๊ฒƒ์ธ๋ฐ, ๊ตฌํ˜„์ด ์‰ฌ์šด ๊ฒƒ์— ๋น„ํ•ด์„œ๋Š” ๊ดœ์ฐฎ์€ ์•”ํ˜ธํ™” ์„ฑ๋Šฅ์„ ์ž๋ž‘ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. password_context๋ฅผ ์„ค์ •ํ–ˆ์œผ๋ฉด, ์ฒ˜์Œ ๊ฐ€์ž…ํ•ด์ค„ ๋•Œ password๋ฅผ ์ด์ชฝ์œผ๋กœ ๋ณด๋‚ด์„œ ์•”ํ˜ธํ™”ํ•œ ๋‹ค์Œ์— database์— ๋“ค์–ด๊ฐ€๋„๋ก ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ์œ ์ € ์ƒ์„ฑ ํ•จ์ˆ˜์™€ ํ•จ๊ป˜ ๋ณด์ž.

# create user
def create_user(db: Session, user: schemas.UserCreate):
    password = utils.get_hashed_password(user.password)
    db_user = models.User(username=user.username, hashed_password=password, email=user.email)
    # database session์— db_user ์ •๋ณด ์ถ”๊ฐ€
    db.add(db_user)
    # db์— ๋ฐ˜์˜
    db.commit()
    # ์ตœ์‹  db ์ •๋ณด 
    db.refresh(db_user)
    return db_user

get_hashed_password ํ•จ์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

def get_hashed_password(password: str) -> str:
    return password_context.hash(password)

์งœ์ž”. ์ด๊ฒŒ ๋์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ๋‚˜์ค‘์— ๋กœ๊ทธ์ธ์„ ํ•  ๋•Œ, ์–ด๋–ป๊ฒŒ ๋งž๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ์ธ์ง€๋ฅผ ํ™•์ธํ• ๊นŒ? ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋„ ์ œ๊ณตํ•œ๋‹ค. ๋กœ๊ทธ์ธ ํ•จ์ˆ˜๋ฅผ ๋ณด์ž.

@app.post("/login")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Session = Depends(get_db)):
    user = user_crud.get_user_by_username(db=db, username=form_data.username)
    if user is None:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
    hashed_pass = user.hashed_password
    if not utils.verify_password(form_data.password, hashed_pass):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
    
    return {
        "access_token": utils.create_access_token(user.username),
        "refresh_token": utils.create_refresh_token(user.username)
    }

๋จผ์ € ์ž…๋ ฅ๋ฐ›์€ form data์—์„œ ์ค‘๋ณต ๋ถˆ๊ฐ€๋Šฅํ•œ username์„ ๊ธฐ๋ฐ˜์œผ๋กœ DB์—์„œ ์œ ์ € ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์œ ์ €์˜ hashed password๋ฅผ ์–ป์–ด์„œ, verify_password ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ํ˜„์žฌ ์ž…๋ ฅ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ๊ฐ™์€ ๊ฐ’์ธ์ง€๋ฅผ ํ™•์ธํ•˜๋„๋ก ๋˜์–ด์žˆ๋‹ค.

form data์˜ annotation์ด OAuth2PasswordRequestForm์œผ๋กœ ๋˜์–ด์žˆ๋Š”๋ฐ, ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํฌํ•จ๋œ... ๋ญ ๊ทธ๋Ÿฐ ๋ณด์•ˆ ์ •๋ณด๋ฅผ form์œผ๋กœ ์ฃผ๊ณ  ๋ฐ›์„ ๋•Œ fastapi.security์—์„œ ์ œ๊ณต๋˜๋Š” form ํ˜•์‹์ธ ๊ฒƒ์œผ๋กœ ๊ธฐ์–ตํ•œ๋‹ค. ์•„๋‹ˆ๋ผ๋ฉด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค... 

def verify_password(password:str, hashed_pass:str) -> bool:
    return password_context.verify(password, hashed_pass)

verify ํ•จ์ˆ˜๋„ passlib์—์„œ ์ œ๊ณตํ•ด์ฃผ๋ฏ€๋กœ ์‚ฌ์šฉ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค. 

 

B. Access Token ๋ถ€์—ฌํ•˜๊ธฐ

Access token์ด ๋ฌด์—‡์ด๋ƒ! ๋‚ด๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€๋ฅผ ์„œ๋ฒ„์— ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ๋Š” ์ผ์ข…์˜ ์‹ ๋ถ„์ฆ์ด๋‹ค. ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ด์šฉ๊ฐ€๋Šฅํ•œ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์ถ•ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋กœ๊ทธ์ธ์„ ํ–ˆ๋Š”์ง€, ์•ˆ ํ–ˆ๋Š”์ง€, ์ด ํŽ˜์ด์ง€๋ฅผ ๋ณผ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€, ์—†๋Š”์ง€๋ฅผ ์ฒดํฌํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. 

Token ์ƒ์„ฑ์—๋Š” HS256 ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์จ์ค„ ๊ฒƒ์ด๋‹ค. ์ด ๋•Œ๋Š” jose์˜ jwt ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ธ๋ฐ, ๋ฏธ๋ฆฌ ์„ ์–ธํ•ด์ค„ ๊ฒƒ์ด 4๊ฐ€์ง€ ์žˆ๋‹ค.

from jose import jwt

ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7
ALGORITHM = "HS256"
SECRET_KEY = "SECRET_KEY"

Access token ๊ณผ refresh token expire minutes๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๋งŒ๋ฃŒ ๊ธฐํ•œ์ด๊ณ , ๋Œ€์ฒด๋กœ  access๋Š” 30๋ถ„, refresh๋Š” ์ผ์ฃผ์ผ์ด๋ผ๊ณ  ํ•ด์„œ ์ผ์ฃผ์ผ๋กœ ์žก์•„๋’€๋‹ค. ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๋งํ•˜๋Š” ๊ฒƒ์ด๊ณ , Secret Key๋Š” ์•”ํ˜ธํ™”ํ•  ๋•Œ ์‚ฌ์šฉํ•  ๊ณ ์œ  ํ‚ค์ธ๋ฐ, ์™ธ๋ถ€๋กœ ๊ณต๊ฐœํ•˜์ง€ ์•Š์•„์•ผํ•œ๋‹ค.(๋‚˜๋Š” ๋‹น์žฅ์€ ์ƒ๊ด€ ์—†์–ด์„œ github์—๋Š” ์˜ฌ๋ ค๋†จ๋‹ค)

์ด์ œ access token ์ƒ์„ฑ, refresh token ์ƒ์„ฑ, ๊ทธ๋ฆฌ๊ณ  access token์„ ํ†ตํ•ด์„œ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์œ ์ €๊ฐ€ ๋ˆ„๊ตฌ์ธ์ง€ ์•Œ์•„๋‚ด๋Š” ํ•จ์ˆ˜๊นŒ์ง€ ์ž‘์„ฑํ•ด๋ณผ ์ฐจ๋ก€๋‹ค. ๋จผ์ € token ์ƒ์„ฑ๋ถ€ํ„ฐ ๋ณด์ž.

def create_access_token(subject: Union[str, Any], expires_delta: int = None) -> str:
    if expires_delta is not None:
        expires_delta = datetime.utcnow() + expires_delta
    else:
        expires_delta = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)

    to_encode = {"exp": expires_delta, 'sub': str(subject)}
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, ALGORITHM)
    return encoded_jwt

def create_refresh_token(subject: Union[str, Any], expires_delta: int = None) -> str:
    if expires_delta is not None:
        expires_delta = datetime.utcnow() + expires_delta
    else:
        expires_delta = datetime.utcnow() + timedelta(minutes=REFRESH_TOKEN_EXPIRE_MINUTES)

    to_encode = {"exp": expires_delta, 'sub': str(subject)}
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, ALGORITHM)
    return encoded_jwt

expires delta๋Š” ์™ธ๋ถ€์—์„œ ๋ฐ›์•„์˜ค์ง€ ์•Š๋„๋ก ํ–ˆ์œผ๋ฏ€๋กœ else๋ฌธ ๋‚ด๋ถ€์˜ ์ •์˜๋กœ ์„ค์ •๋œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์•”ํ˜ธํ™” ์ธ์ฝ”๋”ฉํ•  ๋ฐ์ดํ„ฐ๋ฅผ expires_delta์™€ ์ „๋‹ฌ๋ฐ›์€ subject - ์—ฌ๊ธฐ์—์„œ๋Š” username์„ json string์œผ๋กœ ๋งŒ๋“  ๋’ค ํ† ํฐํ™”ํ•ด์„œ ๋ฆฌํ„ดํ•œ๋‹ค.

 

C. current user ํ™•์ธํ•˜๊ธฐ 

๋‹ค์Œ์€ ํ˜„์žฌ ์ ‘์† ์ค‘์ธ ์œ ์ € ํ™•์ธ์ด๋‹ค. ์ด ๋ถ€๋ถ„์€ access token์„ ๋””์ฝ”๋”ฉํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๋‹ˆ ๋ฐ”๋กœ ๋ณด์ž.

async def get_current_user(token: str = Depends(get_token), db: Session = Depends(get_db)) -> models.User:
    try: 
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        token_data = user_schema.TokenPayload(**payload)

        if datetime.fromtimestamp(token_data.exp) < datetime.now():
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token Expired")
    except(jwt.JWTError, ValidationError):
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Could not validate credentials")
    
    user = user_crud.get_user_by_username(db=db, username=token_data.sub)

    if user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Could not find user")
    
    return user

์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์„ ์ œํ•˜๊ณ  ๋ณธ๋‹ค๋ฉด ๊ฐ„๋‹จํ•˜๋‹ค. jwt์˜ decode๋ฅผ ํ™œ์šฉํ•ด์„œ algorithm๊ณผ secret key๋ฅผ ๋„˜๊ฒจ์ฃผ๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜จ๋‹ค. ๊ทธ๋ฆฌ๊ณ  TokenPayload๋ผ๋Š” ํด๋ž˜์Šค์— ๋ฐ์ดํ„ฐ๋ฅผ ๋งคํ•‘ํ•ด์ค€๋‹ค. TokenPayload๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์„ ์–ธํ•ด์ฃผ์—ˆ๋‹ค.

class TokenPayload(BaseModel):
    sub: str = None
    exp: int = None

์ด ํ† ํฐ ๋ฐ์ดํ„ฐ๋Š” ํ† ํฐ์„ ์ƒ์„ฑํ•  ๋•Œ ์•”ํ˜ธํ™” ์ธ์ฝ”๋”ฉํ•  ๋ฐ์ดํ„ฐ์˜ key๊ฐ’์— ๋งž์ถฐ์„œ ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๊ฑธ ๊ธฐ๋ฐ˜์œผ๋กœ username์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์•„๋‚ด๊ณ , ์œ ์ €๊ฐ€ ์žˆ๋Š”์ง€, ์—†๋Š”์ง€, ์žˆ๋‹ค๋ฉด ๊ทธ ์œ ์ €์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•ด์ค€๋‹ค.

get_token ํ•จ์ˆ˜๋กœ token์„ ๋ฐ›์•„์˜ค๋„๋ก ํ•ด๋’€๋Š”๋ฐ, ์ •์˜๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค. authorization์ด๋ผ๋Š” ์ฟ ํ‚ค ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋ฉด ๊ทธ๊ฒƒ์˜ 6๋ฒˆ์งธ ์ธ๋ฑ์Šค๋ถ€ํ„ฐ๊ฐ€ token์ด์—ˆ๋‹ค.(๊ฝค๋‚˜ ๋…ธ๊ฐ€๋‹ค๋ฅผ ํ–ˆ๋”๋žฌ๋‹ค.)

def get_token(authorization: str = Header(default=None)):
    print(authorization)
    return authorization[6:]

์ด์ œ ๊ธฐ๋Šฅ์€ ๊ตฌํ˜„๋๋‹ค!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! UI์— ์ž…ํ˜€๋ณด๋„๋ก ํ•˜์ž.

 

D. ํ”„๋ก ํŠธ์—”๋“œ ๋งŒ๋“ค๊ธฐ 

์•ˆํƒ€๊น๊ฒŒ๋„ UI ๋””์ž์ธ์—๋Š” ์žฌ๋Šฅ์ด ์—†๋‹ค. ๊ทธ๋ƒฅ ์˜คํ”ˆ๋œ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ํ…œํ”Œ๋ฆฟ์„ ํ•˜๋‚˜ ์ฃผ์›Œ์˜ฌ ๊ฒƒ์ด๋‹ค. 

https://colorlib.com/wp/template/login-form-15/

์ด ๋…€์„์œผ๋กœ ๊ณจ๋ž๋‹ค. ๋กœ๊ทธ์ธ๊ณผ ํšŒ์›๊ฐ€์ž…์€ ์‚ฌ์‹ค์ƒ ๊ฑฐ์˜ ๊ฐ™์€ ํ˜•์‹์„ ๊ฐ–๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, Form data๋ฅผ ์–ด๋–ป๊ฒŒ ์ž˜ api ์ชฝ์œผ๋กœ ๋„˜๊ฒจ์ฃผ๊ณ  ํ™•์ธ์„ ๋ฐ›์•„์˜ค๋ฉด ๋œ๋‹ค. ๋จผ์ € ํšŒ์›๊ฐ€์ž…์ด๋‹ค.

1. ํšŒ์›๊ฐ€์ž…

html ๋ถ€๋ถ„๋ถ€ํ„ฐ ์ž ๊น ๋ณด์ž.

<form action="#" class="signup-form">
  <div class="form-group mt-3">
    <input
      id="username"
      type="text"
      class="form-control"
      required
    />
    <label class="form-control-placeholder" for="username">
      Username
    </label>
  </div>
  <div class="form-group">
    <input
      id="email"
      type="email"
      class="form-control"
      required
    />
    <label class="form-control-placeholder" for="email">
      email
    </label>
  </div>
  <div class="form-group">
    <input
      id="password"
      type="password"
      class="form-control"
      required
    />
    <label class="form-control-placeholder" for="password">
      Password
    </label>
    <span
      toggle="#password-field"
      class="fa fa-fw fa-eye field-icon toggle-password"
    ></span>
  </div>
  <div class="form-group">
    <button
      type="submit"
      class="form-control btn btn-primary rounded submit px-3"
    >
      Sign Up
    </button>
  </div>
</form>

form ํƒœ๊ทธ๋กœ ๋‘˜๋Ÿฌ์‹ธ์—ฌ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋“ค์„ id๋กœ ๊ตฌ๋ถ„์„ ํ•ด์คฌ๋‹ค. ๋Œ€๋ถ€๋ถ„ ์•„์‹œ๊ฒ ์ง€๋งŒ input ํƒœ๊ทธ ๋‚ด๋ถ€ ์†์„ฑ ์ค‘ type์„ ์ง€์ •ํ•˜๋ฉด ํ•ด๋‹นํ•˜๋Š” ํƒ€์ž…์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, email์— email ํƒ€์ž…์„ ๋„ฃ์œผ๋ฉด ~~~~@~~~~.~~ ํ˜•์‹์„ ์•ˆ ์ง€ํ‚ค๋ฉด ํ˜ผ๋‚ด์ค€๋‹ค. 

์ด์ œ jQuery ajax ํŒŒํŠธ๋กœ ๋„˜์–ด๊ฐ€์ž. ํ›—๋‚ ์— ๋น„ํ•˜๋ฉด ๊ฐ„๋‹จํ•˜๊ธฐ ๊ทธ์ง€์—†๋‹ค.

<script type="text/javascript">
  $(document).ready(function ($) {
    $(".signup-form").submit(function (event) {
      event.preventDefault();

      var body = new FormData();
      body.append("username", $("#username").val());
      body.append("password", $("#password").val());
      body.append("email", $("#email").val());

      console.log(body);

      var data = new URLSearchParams(body);

      $.ajax({
        url: "http://localhost:8000/api/users/signup",
        method: "POST",
        data: data,
        contentType: false,
        processData: false,
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
        success: function (response) {
          alert("Account Created");
          location.href = "http://localhost:8000/";
        },
        error: function (response) {
          alert(response["detail"]);
        },
      });

      event.preventDefault();
    });
  });
</script>

๊ฝค ๊ธฐ๋‹ˆ๊นŒ ๋ถ€๋ถ„๋ถ€๋ถ„ ๋œฏ์–ด๋ณด๊ฒ ๋‹ค.

$(".signup-form").submit(function (event) {

์ด form ์ „์ฒด๊ฐ€ ์ œ์ถœ์ด ๋˜๋ฉด ์š” ํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅด๊ฒ ๋‹ค๋Š” ๋œป์ด๋‹ค. ์ œ์ถœ ๋ฒ„ํŠผ์€ ์•„๋ž˜์— ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์คฌ์—ˆ๋‹ค.

var body = new FormData();
body.append("username", $("#username").val());
body.append("password", $("#password").val());
body.append("email", $("#email").val());

var data = new URLSearchParams(body);

key-value๋กœ ๋งค์นญ์„ ์‹œ์ผœ์คฌ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ์„œ๋ฒ„ ์ชฝ์—์„œ ์•„ํ•˜ ์ด๊ฒŒ ๋ฌด์Šจ ๋ฐ์ดํ„ฐ๊ตฌ๋‚˜~ ํ•˜๊ณ  ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค. $("#id").val() ํ˜น์€ $(".class").val() ๋ผ๊ณ  ์จ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ์ฐพ์•„๋ณด๋‹ˆ URLSearchParams๋ผ๋Š” ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•œ๋‹ค๊ณ  ํ•ด์„œ ๋ฌถ์–ด์ฃผ์—ˆ๋‹ค.

$.ajax({
    url: "http://localhost:8000/api/users/signup",
    method: "POST",
    data: data,
    contentType: false,
    processData: false,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    success: function (response) {
      alert("Account Created");
      location.href = "http://localhost:8000/";
    },
    error: function (response) {
      alert(response["detail"]);
    },
  });

์ด์ œ ajax๋กœ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ–๋‹ค๊ฐ€ api ๋ผ์šฐํ„ฐ ์–ด๋”˜๊ฐ€๋กœ ๋‚ ๋ ค์ค€๋‹ค. ๋ฐ”๋กœ signup ์œผ๋กœ! post ๋ฐฉ์‹๊นŒ์ง€ ์ง€์ •ํ•ด์„œ ๋ณด๋‚ด์ฃผ๋ฉด 

@router.post("/signup") #, response_model=schemas.User)
def create_user(username: Annotated[str, Form()], email: Annotated[str, Form()], password: Annotated[str, Form()], db: Session = Depends(get_db)):
    db_user = crud.get_user_by_username(db=db, username=username)
    if db_user:
        raise HTTPException(status_code=400, detail="Email already registered.")
    user = schemas.UserCreate(username=username, email=email, password=password)
    return crud.create_user(db=db, user=user)

์ด ์นœ๊ตฌ๊ฐ€ ๋ฐ›์•„๋จน๊ณ   create user๋ฅผ ํ•ด์ฃผ๋˜์ง€, email ์ด๋ฏธ ์žˆ์–ด์„œ ์•ˆ๋œ๋‹ค๊ณ  ํ•ด์ฃผ๋˜์ง€ ํ•˜๊ฒŒ ๋œ๋‹ค. 

์„ฑ๊ณตํ–ˆ์„ ๋•Œ ํ™ˆ ํ™”๋ฉด์œผ๋กœ ๊ฐ€๊ณ , ์‹คํŒจํ–ˆ์œผ๋ฉด ์•ˆ๋๋‹ค๊ณ  alert๋ฅผ ๋ณด๋‚ด์ฃผ๊ณ  ์—ฌ๊ธฐ ๋จธ๋ฌด๋ฅธ๋‹ค. ์ ์šฉ ํ™”๋ฉด์„ ํ•จ ๋ณด์ž.

์ด๋ฏธ ์žˆ๋Š” ์œ ์ € ๋ฐ์ดํ„ฐ๋กœ ํ–ˆ์„ ๋•Œ
์ƒˆ ๋…€์„์œผ๋กœ ํ–ˆ์„ ๋•Œ

DB๋ฅผ ๋ณด๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๋„ ์ž˜ ์•”ํ˜ธํ™”๋ผ์„œ ์ €์žฅ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ข‹๋‹ค. ์ด์ œ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋งŒ ํ›„๋”ฑ ๋งŒ๋“ค์–ด๋ณธ๋‹ค.

 

2. ๋กœ๊ทธ์ธ

์†”์งํžˆ ๋งํ•˜๋ฉด sign up๊ณผ ๋‹ค๋ฅธ ๊ฑฐ๋ผ๊ณค ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜์™€ return๊ฐ’ ๋ฟ์ด๋‹ค. ๋ฐ”๋กœ jQuery๋ถ€ํ„ฐ ๋ณด๋„๋ก ํ•˜๊ฒ ๋‹ค. 

<script
  type="text/javascript"
  src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"
></script>
<script type="text/javascript">
  $(document).ready(function ($) {
    $.cookie();
    $(".signin-form").submit(function (event) {
      event.preventDefault();

      var body = new FormData();
      body.append("username", $("#username").val());
      body.append("password", $("#password").val());

      var data = new URLSearchParams(body);

      $.ajax({
        url: "http://localhost:8000/login",
        method: "POST",
        data: data,
        contentType: false,
        processData: false,
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
        success: function (response) {
          $.cookie("access_token", response.access_token);
          location.href = "http://localhost:8000/dashboard";
        },
        error: function (response) {
          alert(response.responseJSON.detail);
          console.log(response);
        },
      });

      event.preventDefault();
    });

    $("#to-signup").on("click", function () {
      location.href = "http://localhost:8000/signup";
    });
  });
</script>

ํ•˜๋‚˜ ์ค‘์š”ํ•œ ๊ฒƒ์€, cookie ํŒŒ์ผ์ด ์žˆ์–ด์•ผํ•œ๋‹ค. 

<script
  type="text/javascript"
  src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"
></script>

์ด๋Ÿฐ ์‹์œผ๋กœ ์‚ฝ์ž…์„ ํ•ด์ฃผ๊ณ !!!!!!!! document ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋œ ํ›„์— cookie ์ดˆ๊ธฐํ™”๋„ ํ•ด์ฃผ๊ณ !!! ๊ทธ๋ฆฌ๊ณ  ๋‚˜์„œ ๋กœ๊ทธ์ธ ํ•จ์ˆ˜๋ฅผ ๋„ฃ์–ด์ค€๋‹ค. ๋ฐ์ดํ„ฐ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ์œ„์™€ ๊ฐ™์œผ๋‹ˆ ์ƒ๋žตํ•˜๊ณ  ajax๋งŒ ๋ณด์ž.

$.ajax({
    url: "http://localhost:8000/login",
    method: "POST",
    data: data,
    contentType: false,
    processData: false,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    success: function (response) {
      $.cookie("access_token", response.access_token);
      location.href = "http://localhost:8000/dashboard";
    },
    error: function (response) {
      alert(response.responseJSON.detail);
      console.log(response);
    },
  });

์šฐ์„  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” url์ด ๋‹ฌ๋ผ์กŒ๋‹ค. login router ์ฃผ์†Œ๋กœ ๋ณด๋‚ด์ฃผ๋ฉด

@app.post("/login")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()], db: Session = Depends(get_db)):
    user = user_crud.get_user_by_username(db=db, username=form_data.username)
    if user is None:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
    hashed_pass = user.hashed_password
    if not utils.verify_password(form_data.password, hashed_pass):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Incorrect email or password")
    
    return {
        "access_token": utils.create_access_token(user.username),
        "refresh_token": utils.create_refresh_token(user.username)
    }

์ด ์นœ๊ตฌ๊ฐ€ ๋ฐ›๋Š”๋‹ค. ์‹น ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์„œ ์ž…๋ ฅ ์ •๋ณด๊ฐ€ ์˜ฌ๋ฐ”๋ฅด๋ฉด ์ƒ์„ฑ๋œ access token๊ณผ refresh token์„ ๋ฐ›์•„์˜ค๊ณ , cookie์— access token์„ ์ž…๋ ฅํ•ด๋‘”๋‹ค. ๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ๋˜๋А๋ƒ.

๋กœ๊ทธ์ธ์„ ์„ฑ๊ณต์‹œํ‚ค๊ณ  F12๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ๊ฐœ๋ฐœ์ž ๋„๊ตฌ - Application - Cookies๋ฅผ ๋ณด๋ฉด 

Access token์ด ์žˆ๋‹ค! ์ด ๋†ˆ์œผ๋กœ ์ด์ œ ๋‹ค์Œ ํฌ์ŠคํŒ…์— ๋งŒ๋“ค monitoring system์˜ ์—ด๋žŒ ๊ถŒํ•œ์„ ํ™•์ธํ•ด์ค„ ๊ฒƒ์ด๋‹ค. 

ํ† ํฐ์ด ์—†๋‹ค๋ฉด ์ด๋Ÿฐ ๋‚ด์šฉ์ด ๋œจ๋„๋ก! 

ํ•˜,,, ์ฐธ ๊ธด ํฌ์ŠคํŒ…์ด์—ˆ๋‹ค. ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” Google chart๋ฅผ ํ™œ์šฉํ•ด์„œ ์˜ˆ์˜๊ฒŒ ๊ทธ๋ž˜ํ”„ ๊ทธ๋ฆฌ๊ธฐ... ๊ทธ๋ฆฌ๊ณ  ์—ด๋žŒ๊ถŒํ•œ ํ™•์ธํ•˜๊ธฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋Œ์•„์˜ค๊ฒ ๋‹ค.

ํ‹€๋ ธ๊ฑฐ๋‚˜ ๋ถ€๊ฐ€์ ์œผ๋กœ ์ •๋ณด๋ฅผ ์ฃผ๊ณ  ์‹ถ์œผ์‹  ๋ถ„์ด ๊ณ„์‹œ๋‹ค๋ฉด ์–ธ์ œ๋‚˜ ๋Œ“๊ธ€ ํ”ผ๋“œ๋ฐฑ์€ ํ™˜์˜์ž…๋‹ˆ๋‹ค!

728x90
์ €์ž‘์žํ‘œ์‹œ ๋น„์˜๋ฆฌ ๋ณ€๊ฒฝ๊ธˆ์ง€ (์ƒˆ์ฐฝ์—ด๋ฆผ)

'๐ŸŽผ Project > ๐ŸงŠ Monitoring System' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Monitoring System] 7. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. Google Chart) (3) - ๋งˆ์นจ๋‚ด ๊ทธ๋ž˜ํ”„ ๊ทธ๋ฆฌ๊ธฐ ๅฎŒ  (0) 2023.09.25
[Monitoring System] 5. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (1)  (0) 2023.06.13
[Monitoring System] 4.1 ์„ธ์ƒ์— ๋” ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ด - pyqtgraph  (2) 2023.04.27
[Monitoring System] 4. UDP ํ†ต์‹ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ง„๋™ ๋ฐ์ดํ„ฐ FFT / STFT์‹œ๊ฐํ™”(numpy, tensorflow, pytorch)  (0) 2023.04.25
[Monitoring System] 3. UDP ํ†ต์‹ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ง„๋™ ๋ฐ์ดํ„ฐ Plotting(feat. matplotlib)  (0) 2023.04.19
'๐ŸŽผ Project/๐ŸงŠ Monitoring System' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [Monitoring System] 7. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. Google Chart) (3) - ๋งˆ์นจ๋‚ด ๊ทธ๋ž˜ํ”„ ๊ทธ๋ฆฌ๊ธฐ ๅฎŒ
  • [Monitoring System] 5. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (1)
  • [Monitoring System] 4.1 ์„ธ์ƒ์— ๋” ๋น ๋ฅธ ๋ฐฉ๋ฒ•์ด - pyqtgraph
  • [Monitoring System] 4. UDP ํ†ต์‹ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ง„๋™ ๋ฐ์ดํ„ฐ FFT / STFT์‹œ๊ฐํ™”(numpy, tensorflow, pytorch)
darly213
darly213
ํ˜ธ๋ฝํ˜ธ๋ฝํ•˜์ง€ ์•Š์€ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ณด์ž
  • darly213
    ERROR DENY
    darly213
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (97)
      • ๐Ÿฌ ML & Data (50)
        • ๐ŸŒŠ Computer Vision (2)
        • ๐Ÿ“ฎ Reinforcement Learning (12)
        • ๐Ÿ“˜ ๋…ผ๋ฌธ & ๋ชจ๋ธ ๋ฆฌ๋ทฐ (8)
        • ๐Ÿฆ„ ๋ผ์ดํŠธ ๋”ฅ๋Ÿฌ๋‹ (3)
        • โ” Q & etc. (5)
        • ๐ŸŽซ ๋ผ์ดํŠธ ๋จธ์‹ ๋Ÿฌ๋‹ (20)
      • ๐Ÿฅ Web (21)
        • โšก Back-end | FastAPI (2)
        • โ›… Back-end | Spring (5)
        • โ” Back-end | etc. (9)
        • ๐ŸŽจ Front-end (4)
      • ๐ŸŽผ Project (8)
        • ๐ŸงŠ Monitoring System (8)
      • ๐Ÿˆ Algorithm (0)
      • ๐Ÿ”ฎ CS (2)
      • ๐Ÿณ Docker & Kubernetes (3)
      • ๐ŸŒˆ DEEEEEBUG (2)
      • ๐ŸŒ  etc. (8)
      • ๐Ÿ˜ผ ์‚ฌ๋‹ด (1)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ๋ฐฉ๋ช…๋ก
    • GitHub
    • Notion
    • LinkedIn
  • ๋งํฌ

    • Github
    • Notion
  • ๊ณต์ง€์‚ฌํ•ญ

    • Contact ME!
  • 250x250
  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
darly213
[Monitoring System] 6. Fast API ๋กœ ์ดํ‹€๋งŒ์— ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ(feat. SQLAlchemy, PostgreSQL) (2) - MQTT ๋ฐ์ดํ„ฐ ์ ์žฌ / ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”