μ€μκ° μ£Όμ λ°μ΄ν°μ μ¬μ©μ μΈμ¦μ΄ ν΅ν©λ μ’ ν© μ£Όμ κ±°λ νλ«νΌμ λλ€. WebSocketμ ν΅ν μ€μκ° λ°μ΄ν° μ€νΈλ¦¬λ°, μ±ν μμ€ν , λͺ¨μν¬μ κΈ°λ₯μ μ 곡ν©λλ€.
- μ¬μ©μ μΈμ¦: JWT + μΉ΄μΉ΄μ€ μμ λ‘κ·ΈμΈ
- μ€μκ° μ£Όμ λ°μ΄ν°: 50κ° μ£Όμ μ£Όμ + 10κ° μνΈνν
- WebSocket μ€μκ° μ€νΈλ¦¬λ°: μμΈ λ°μ΄ν° + μ±ν
- μ€μκ° μ±ν : μ’ λͺ©λ³ μ±ν λ°©
- λͺ¨μν¬μ: κ°μ κ±°λ μμ€ν
- AI μ±λ΄: OpenAI κΈ°λ° ν¬μ μλ΄
- Framework: FastAPI 0.104+
- Database: MySQL 8.0 + SQLAlchemy ORM
- Authentication: JWT + OAuth2 (μΉ΄μΉ΄μ€)
- Real-time: WebSocket
- External APIs: Finnhub (μ£Όμ), OpenAI (μ±λ΄)
- Environment: Python 3.9+
- Framework: React 18+ / Next.js 13+
- Real-time: WebSocket Client
- State Management: Redux Toolkit / Zustand
- UI: Material-UI / Tailwind CSS
- Charts: Chart.js / Recharts
juda/
βββ stock/backend/ # λ°±μλ 루νΈ
β βββ main.py # FastAPI μ± μ§μ
μ
β βββ core/
β β βββ config.py # νκ²½ μ€μ
β βββ auth/ # μΈμ¦ μμ€ν
β β βββ auth_routes.py # μΈμ¦ API λΌμ°ν°
β β βββ auth_service.py # JWT ν ν° κ΄λ¦¬
β β βββ kakao_service.py # μΉ΄μΉ΄μ€ OAuth
β β βββ models.py # μ¬μ©μ λͺ¨λΈ
β β βββ schemas.py # Pydantic μ€ν€λ§
β βββ api/ # REST API
β β βββ stock.py # μ£Όμ λ°μ΄ν° API
β β βββ chat.py # μ±ν
API
β βββ services/ # λΉμ¦λμ€ λ‘μ§
β β βββ stock_service.py # μ£Όμ λ°μ΄ν° μ²λ¦¬
β β βββ auto_collector.py # μλ λ°μ΄ν° μμ§
β β βββ cache_service.py # μΊμ± κ΄λ¦¬
β βββ websocket_routes.py # WebSocket μλν¬μΈνΈ
β βββ websocket_manager.py # WebSocket μ°κ²° κ΄λ¦¬
β βββ stockDeal/ # λͺ¨μν¬μ
β β βββ mock_investment.py # κ°μ κ±°λ
β βββ chatbot/ # AI μ±λ΄
β β βββ chat_router.py # μ±λ΄ API
β βββ database.py # DB μ€μ λ° λͺ¨λΈ
β βββ utils/
β β βββ logger.py # λ‘κΉ
μ€μ
β βββ test/ # ν
μ€νΈ λꡬ
β β βββ test_stock_quote_api.py
β β βββ fix_crypto_table.py
β βββ docs/ # API λ¬Έμ
β βββ websocket_usage.md
β βββ websocket_chat_usage.md
β βββ stock_quote_api_usage.md
β βββ frontend_integration_guide.md
βββ .env # νκ²½λ³μ μ€μ
βββ README.md # μ΄ νμΌ
# Python 3.9+ νμ
python --version
# MySQL 8.0+ νμ
mysql --versiongit clone <repository-url>
cd juda
# κ°μνκ²½ μμ± (κΆμ₯)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# μμ‘΄μ± μ€μΉ
pip install fastapi[all] sqlalchemy pymysql python-multipart
pip install requests python-jose[cryptography] passlib[bcrypt]
pip install python-dotenv websockets openai.env νμΌμ μμ±νκ³ λ€μ λ΄μ©μ μ€μ νμΈμ:
# λ°μ΄ν°λ² μ΄μ€ μ€μ
DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=your_mysql_password
DB_NAME=stock_db
# API ν€
FINNHUB_API_KEY=your_finnhub_api_key
OPENAI_API_KEY=your_openai_api_key
# JWT μΈμ¦
JWT_SECRET_KEY=your_very_secure_random_secret_key_here
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440
# μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ (μ νμ¬ν)
KAKAO_CLIENT_ID=your_kakao_app_key
KAKAO_REDIRECT_URI=https://dajutalk.com/auth/kakao/callback
# νλ‘ νΈμλ URL
FRONTEND_URL=https://dajutalk.com
# κ°λ° νκ²½μμλ λ€μκ³Ό κ°μ΄ μ€μ
# KAKAO_REDIRECT_URI=http://localhost:8000/auth/kakao/callback
# FRONTEND_URL=http://localhost:3000# MySQLμ λ°μ΄ν°λ² μ΄μ€ μμ±
mysql -u root -p
CREATE DATABASE stock_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
exit
# ν
μ΄λΈ μλ μμ± (μλ² μμ μ μλμΌλ‘ μμ±λ¨)- https://finnhub.io νμκ°μ
- API ν€ λ°κΈ
.envνμΌμFINNHUB_API_KEYμ€μ
- https://openai.com κ³μ μμ±
- API ν€ λ°κΈ
.envνμΌμOPENAI_API_KEYμ€μ
- https://developers.kakao.com μ± μμ±
- JavaScript ν€ λ³΅μ¬
.envνμΌμKAKAO_CLIENT_IDμ€μ
cd stock/backend
python main.py
# λλ uvicornμΌλ‘ μ€ν
uvicorn stock.backend.main:app --host 0.0.0.0 --port 8000 --reloadμλ² μ€ν ν λ€μ URLμμ νμΈ:
- λ©μΈ API: https://dajutalk.com
- API λ¬Έμ: https://dajutalk.com/docs
- ν¬μ€ 체ν¬: https://dajutalk.com/health
| Method | Endpoint | μ€λͺ |
|---|---|---|
| POST | /auth/signup |
νμκ°μ |
| POST | /auth/login |
λ‘κ·ΈμΈ |
| POST | /auth/logout |
λ‘κ·Έμμ |
| GET | /auth/me |
νμ¬ μ¬μ©μ μ 보 |
| POST | /auth/check-email |
μ΄λ©μΌ μ€λ³΅ νμΈ |
| POST | /auth/check-nickname |
λλ€μ μ€λ³΅ νμΈ |
| GET | /auth/kakao/redirect |
μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ μμ |
| Method | Endpoint | μ€λͺ |
|---|---|---|
| GET | /api/stocks/quote?symbol=AAPL |
κ°λ³ μ£Όμ μμΈ |
| GET | /api/stocks/history/{symbol}?hours=24 |
μ£Όμ νμ€ν 리 |
| GET | /api/stocks/symbols |
μ§μ μ£Όμ λͺ©λ‘ |
| GET | /api/stocks/crypto/{symbol} |
μνΈνν μμΈ |
| GET | /api/stocks/crypto/symbols |
μ§μ μνΈνν λͺ©λ‘ |
| Method | Endpoint | μ€λͺ |
|---|---|---|
| GET | /api/chat/rooms |
νμ± μ±ν λ°© λͺ©λ‘ |
| GET | /api/chat/rooms/{symbol} |
νΉμ μ±ν λ°© μ 보 |
| Method | Endpoint | μ€λͺ |
|---|---|---|
| POST | /mock/buy |
κ°μ λ§€μ |
| POST | /mock/sell |
κ°μ λ§€λ |
| GET | /mock/portfolio |
ν¬νΈν΄λ¦¬μ€ μ‘°ν |
| GET | /mock/balance |
κ°μ μκ³ μ‘°ν |
| Method | Endpoint | μ€λͺ |
|---|---|---|
| POST | /chatbot/chat |
AI ν¬μ μλ΄ |
# μ 체 μμ₯ λ°μ΄ν°
wss://dajutalk.com/ws/main
# κ°λ³ μ£Όμ λ°μ΄ν°
wss://dajutalk.com/ws/stocks?symbol=AAPL
# κ°λ³ μνΈνν λ°μ΄ν°
wss://dajutalk.com/ws/crypto?symbol=BTC# μ’
λͺ©λ³ μ±ν
λ°©
wss://dajutalk.com/ws/chat?symbol=AAPL&nickname=μ¬μ©μλͺ
&user_id=123// νμκ°μ
const formData = new FormData();
formData.append('email', 'user@example.com');
formData.append('password', 'password123');
formData.append('nickname', 'μ¬μ©μ');
fetch('https://dajutalk.com/auth/signup', {
method: 'POST',
body: formData,
credentials: 'include' // μ€μ: μΏ ν€ ν¬ν¨
});
// λ‘κ·ΈμΈ
const formData = new FormData();
formData.append('email', 'user@example.com');
formData.append('password', 'password123');
fetch('https://dajutalk.com/auth/login', {
method: 'POST',
body: formData,
credentials: 'include'
});// μ 체 μμ₯ λ°μ΄ν° ꡬλ
const socket = new WebSocket('wss://dajutalk.com/ws/main');
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'market_update') {
// UI μ
λ°μ΄νΈ
updateStockData(data.data.stocks);
updateCryptoData(data.data.cryptos);
}
};
// κ°λ³ μ’
λͺ© ꡬλ
const appleSocket = new WebSocket('wss://dajutalk.com/ws/stocks?symbol=AAPL');// μ±ν
λ°© μ°κ²°
const chatSocket = new WebSocket('wss://dajutalk.com/ws/chat?symbol=AAPL&nickname=μ¬μ©μ&user_id=123');
// λ©μμ§ μ μ‘
chatSocket.send(JSON.stringify({
type: 'chat_message',
message: 'μλ
νμΈμ!'
}));
// λ©μμ§ μμ
chatSocket.onmessage = (event) => {
const data = JSON.parse(event.data);
displayMessage(data.username, data.message);
};cd stock/backend/test
# λ¨μΌ μ‘°ν
python test_stock_quote_api.py AAPL
# μ€μκ° λͺ¨λν°λ§ (5μ΄ κ°κ²©)
python test_stock_quote_api.py AAPL --monitor --interval 5
# νΉμ μκ° λμ λͺ¨λν°λ§
python test_stock_quote_api.py AAPL --monitor --duration 60# μνΈνν ν
μ΄λΈ μ¬μμ±
python test/fix_crypto_table.pyνλ‘μ νΈμ κ° κΈ°λ₯μ λν μμΈν λ¬Έμλ docs/ ν΄λμμ νμΈν μ μμ΅λλ€:
νλ‘λμ νκ²½ (κΈ°λ³Έκ°)
# API URL
API_URL=https://dajutalk.com
# WebSocket URL
WS_URL=wss://dajutalk.comκ°λ° νκ²½
# API URL
API_URL=http://localhost:8000
# WebSocket URL
WS_URL=ws://localhost:8000κΈ°λ³Έμ μΌλ‘ λ€μ λλ©μΈμ΄ νμ©λ©λλ€:
https://dajutalk.comhttps://www.dajutalk.comhttp://localhost:3000(κ°λ°μ©)http://localhost:3001(κ°λ°μ©)
λͺ¨λ λ‘κ·Έλ μ½μμ μΆλ ₯λλ©°, λ€μ λ λ²¨λ‘ κ΅¬λΆλ©λλ€:
- INFO: μΌλ° μ 보
- WARNING: κ²½κ³ μ¬ν
- ERROR: μ€λ₯ λ°μ
-
λ°μ΄ν°λ² μ΄μ€ μ°κ²° μ€ν¨
- MySQL μλ² μ€ν νμΈ
.envνμΌμ DB μ€μ νμΈ
-
API ν€ μ€λ₯
- Finnhub API ν€ μ ν¨μ± νμΈ
- API μ¬μ©λ μ ν νμΈ
-
WebSocket μ°κ²° λκΉ
- λ°©νλ²½ μ€μ νμΈ
- νλ‘μ μ€μ νμΈ
-
μΉ΄μΉ΄μ€ λ‘κ·ΈμΈ μ€ν¨
- μΉ΄μΉ΄μ€ μ± μ€μ μ λλ©μΈ λ±λ‘ νμΈ (dajutalk.com)
- redirect_uri μ νμ± νμΈ (https://dajutalk.com/auth/kakao/callback)
# μλ² λ‘κ·Έ μ€μκ° νμΈ
tail -f /var/log/stock-api.log
# λλ μ½μμμ μ§μ νμΈ
python main.py-
νκ²½λ³μ μ€μ
# νλ‘λμ νκ²½ (κΈ°λ³Έκ°) KAKAO_REDIRECT_URI=https://dajutalk.com/auth/kakao/callback FRONTEND_URL=https://dajutalk.com # κ°λ° νκ²½ KAKAO_REDIRECT_URI=http://localhost:8000/auth/kakao/callback FRONTEND_URL=http://localhost:3000
-
HTTPS μ€μ
- SSL μΈμ¦μ μ€μ
- 보μ μΏ ν€ νμ±ν
-
λ°μ΄ν°λ² μ΄μ€ μ΅μ ν
- μΈλ±μ€ μ€μ
- 컀λ₯μ ν μ‘°μ
-
λͺ¨λν°λ§ μ€μ
- λ‘κ·Έ μμ§ μμ€ν ꡬμΆ
- μ±λ₯ λͺ¨λν°λ§ λꡬ μ°λ
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€λ‘ μ 곡λ©λλ€.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
λ¬Έμ κ° λ°μνκ±°λ μ§λ¬Έμ΄ μμΌμλ©΄ GitHub Issuesλ₯Ό ν΅ν΄ λ¬Έμν΄ μ£ΌμΈμ.
μ΅μ’ μ λ°μ΄νΈ: 2025λ 06μ