백엔드 전체 구현 완료: 내부 서비스(Auth, Client, Realtime), API 엔드포인트 및 스케줄러 구현
This commit is contained in:
117
backend/app/services/kis_auth.py
Normal file
117
backend/app/services/kis_auth.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import httpx
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import select
|
||||
from app.db.database import SessionLocal
|
||||
from app.db.models import ApiSettings
|
||||
|
||||
class KisAuth:
|
||||
BASE_URL_REAL = "https://openapi.koreainvestment.com:9443"
|
||||
# BASE_URL_VIRTUAL = "https://openapivts.koreainvestment.com:29443"
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
async def get_access_token(self, db_session=None) -> str:
|
||||
"""
|
||||
Returns valid access token. Issues new one if expired or missing.
|
||||
"""
|
||||
local_session = False
|
||||
if not db_session:
|
||||
db_session = SessionLocal()
|
||||
local_session = True
|
||||
|
||||
try:
|
||||
# 1. Get Settings
|
||||
stmt = select(ApiSettings).where(ApiSettings.id == 1)
|
||||
result = await db_session.execute(stmt)
|
||||
settings_obj = result.scalar_one_or_none()
|
||||
|
||||
if not settings_obj or not settings_obj.appKey or not settings_obj.appSecret:
|
||||
raise ValueError("KIS API Credentials not configured.")
|
||||
|
||||
# 2. Check Expiry (Buffer 10 mins)
|
||||
if settings_obj.accessToken and settings_obj.tokenExpiry:
|
||||
if settings_obj.tokenExpiry > datetime.now() + timedelta(minutes=10):
|
||||
return settings_obj.accessToken
|
||||
|
||||
# 3. Issue New Token
|
||||
token_data = await self._issue_token(settings_obj.appKey, settings_obj.appSecret)
|
||||
|
||||
# 4. Save to DB
|
||||
settings_obj.accessToken = token_data['access_token']
|
||||
# expires_in is seconds (usually 86400)
|
||||
settings_obj.tokenExpiry = datetime.now() + timedelta(seconds=int(token_data['expires_in']))
|
||||
|
||||
await db_session.commit()
|
||||
return settings_obj.accessToken
|
||||
|
||||
except Exception as e:
|
||||
await db_session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
if local_session:
|
||||
await db_session.close()
|
||||
|
||||
async def _issue_token(self, app_key: str, app_secret: str) -> dict:
|
||||
url = f"{self.BASE_URL_REAL}/oauth2/tokenP"
|
||||
payload = {
|
||||
"grant_type": "client_credentials",
|
||||
"appkey": app_key,
|
||||
"appsecret": app_secret
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.post(url, json=payload, headers={"Content-Type": "application/json"})
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
async def get_approval_key(self, db_session=None) -> str:
|
||||
"""
|
||||
Returns WebSocket Approval Key. Issues new one if missing.
|
||||
"""
|
||||
local_session = False
|
||||
if not db_session:
|
||||
db_session = SessionLocal()
|
||||
local_session = True
|
||||
|
||||
try:
|
||||
stmt = select(ApiSettings).where(ApiSettings.id == 1)
|
||||
result = await db_session.execute(stmt)
|
||||
settings_obj = result.scalar_one_or_none()
|
||||
|
||||
if not settings_obj or not settings_obj.appKey or not settings_obj.appSecret:
|
||||
raise ValueError("KIS API Credentials not configured.")
|
||||
|
||||
if settings_obj.websocketApprovalKey:
|
||||
return settings_obj.websocketApprovalKey
|
||||
|
||||
# Issue New Key
|
||||
approval_key = await self._issue_approval_key(settings_obj.appKey, settings_obj.appSecret)
|
||||
|
||||
settings_obj.websocketApprovalKey = approval_key
|
||||
await db_session.commit()
|
||||
|
||||
return approval_key
|
||||
|
||||
except Exception as e:
|
||||
await db_session.rollback()
|
||||
raise e
|
||||
finally:
|
||||
if local_session:
|
||||
await db_session.close()
|
||||
|
||||
async def _issue_approval_key(self, app_key: str, app_secret: str) -> str:
|
||||
url = f"{self.BASE_URL_REAL}/oauth2/Approval"
|
||||
payload = {
|
||||
"grant_type": "client_credentials",
|
||||
"appkey": app_key,
|
||||
"secretkey": app_secret # Note: Parameter name difference
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.post(url, json=payload, headers={"Content-Type": "application/json"})
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return data['approval_key']
|
||||
|
||||
kis_auth = KisAuth()
|
||||
Reference in New Issue
Block a user