애 먹었던 디렉토리 별 모듈정리를 드디어 끝냈다. 정리하면서 느낀 과정은, 확실히 만들기 전부터 분리해놓고 구상하는 것이 훨씬 더 유지보수에 좋다고 느낀 것이다. 처음에 한 코드에 다 때려박았을 때는, 에러가 났을 때 고치기가 너무 힘들었다. 지금도 보면 토나온다... 근데 정리하고 나서는, 에러가 무섭지 않다 이게 가장 크게 느낀 점 같다. 에러가 나오면 어디서 났지를 너무 쉽게 찾고 접근하게 된다. 유지보수가 쉽다는 말이 괜히 있는 것이 아니다.. 그러다 보니 기능추가가 자연스레 쉬워지고, 루프하는 함수를 정리하게 되는 습관이 생겼다. CSS에도 눈을 돌리는 중이다. 어쨌든 좀 이쁘게 사이트를 만들고 싶기에 ㅎ..
db구현도 천천히 해나가며, 후에는 db와 소통하며 편리하게 만들 예정이다. 추가하고 싶은 기능이라 함은, 회원가입제, 게시판, 분석기능 및 관련 자료 업데이트 등등이 있다. 진짜 별거 아닌 것 같은데 학교다니면서 구현하려면 한 세월일 것 같다. 그래도 개인프로젝트 시작한거 되게 깔끔하게 마무리하고 싶은 생각이 강하다. 쓰다보니 일기가 되긴 했는데, 이제부터 정리본을 업로드 해보자.
(기본적인 디렉토리 구성이다.)
RiotProject
├── app
│ ├── controller
│ │ ├── controller.py
│ │ └── roop.py
│ ├── model
│ │ └── model.py
│ ├── env
│ │ ├── dict.py
│ │ └── URL.py
│ ├── view
│ │ └── view.py
│ └── order.py
├── run.py
└── prototype.py
from flask import Flask, url_for, redirect
from RiotApiProject.app.view import view
# import os / 디렉토리 절대 경로 이게 뭔지 공부해보기
# 템플릿 분리해서 사용해보기
# 데이터베이스 구현하기(controller model)
# view에서 데이터 입력받음 >> 컨트롤러로 모듈함수 호출 >> 컨트롤러에서 데이터 처리 및 조회 리턴 >> 값을 저장 >> 템플릿 랜더링
app = Flask(__name__)
app.register_blueprint(view.bp)
@app.route('/')
def create():
print('hello site')
return redirect(url_for('main.input'))
#bp함수로 호출할 수 있다 이렇게하면.
if __name__ == '__main__':
app.run(debug=True)
query1 = "SELECT id, puuid FROM summoners WHERE nickname =?"
#controller.py
from RiotApiProject.app.env.URL import *
from RiotApiProject.app.env.dict import rank
from RiotApiProject.app.model import model as m
from RiotApiProject.app.controller import roop as r
import requests
# APIS spectator-v4 banned champ / gamelength /summonerspell / championid /teamid
# match v-5 participantDTO 분석할 수 있는 데이터 진짜 많음. . banDTO / objectiveDTO
api_key = Api
def search(summoner_name):
# db 연결
conn, cursor = m.get_db_cursor()
m.create_database(conn, cursor)
# db에서 데이터 확인
id, puuid = m.db_check(cursor, summoner_name)
if id and puuid:
summoner_info = r.get_info(summoner_name)
summoner_puuid = puuid
summoner_rank = r.get_rank(id) #랭크정보 조회
r.roop(summoner_rank)
return summoner_info, rank, summoner_puuid
else:
summoner_info = r.get_info(summoner_name) # summoner_info 저장
summoner_puuid = summoner_info['puuid']
id = summoner_info['id']
summoner_rank = r.get_rank(id)
r.roop(summoner_rank)
m.save_info(conn, cursor, summoner_info)
return summoner_info, rank, summoner_puuid
def ingame(puuid, match_type):
if match_type == '솔로랭크':
match_type = 420 # 솔로랭크 엔드포인트
else:
match_type = 440 # 자유랭크 엔드포인트
match_url = f'{matchnumL}{puuid}/ids?queue={match_type}&type=ranked&start=0&count=10&api_key={api_key}'
response = requests.get(match_url)
match_id = response.json()
match_data = []
for m in match_id:
base_url = f'{matchL}{m}?api_key={api_key}'
r = requests.get(base_url)
Match_Dto = r.json()
Match_info = Match_Dto['info']
Participants = Match_info['participants']
for participant in Participants:
if participant['puuid'] == puuid:
w = participant['win']
k = participant['kills']
d = participant['deaths']
a = participant['assists']
mdtoc = participant['magicDamageDealtToChampions']
pdtoc = participant['physicalDamageDealtToChampions']
win = 'Win' if w else 'Loss'
# zero divisionerror
try:
kda = (k + a) / d
kda = round(kda, 2)
except ZeroDivisionError:
kda = 999
match_data.append({
'Match_id': m,
'Win': win,
'Kills': k,
'Deaths': d,
'Assists': a,
'Kda': kda,
'PhysicalDtoC': pdtoc,
'MagicDtoC': mdtoc
})
return match_data
import requests
from RiotApiProject.app.env.dict import rank
from RiotApiProject.app.env.URL import *
api_key = Api
def roop(summoner_rank):
print(summoner_rank)
for entry in summoner_rank:
print(entry)
rank['queue_type'] = entry["queueType"]
rank['tier'] = entry["tier"]
rank['match'] = entry["rank"]
rank['leaguepoints'] = entry["leaguePoints"]
rank['wins'] = entry["wins"]
rank['losses'] = entry["losses"]
pass
def get_info(summoner_name):
summonerV4_url = summonerV4
url = f'{summonerV4_url}{summoner_name}?api_key={api_key}'
response = requests.get(url)
summoner_info = response.json()
return summoner_info
def get_rank(id):
league = leagueV4
leagueV4_url = f'{league}{id}?api_key={api_key}'
response = requests.get(leagueV4_url)
summoner_rank = response.json()
return summoner_rank
import sqlite3
from RiotApiProject.app import order as o
# 데이터베이스 구축
# 조회 및 업데이트 기능 구현
def get_db_cursor():
try:
conn = sqlite3.connect('lol.db')
cursor = conn.cursor()
return conn, cursor
except Exception as e:
print(f"Database connection error: {e}")
return None, None
def db_check(cursor, name):
cursor.execute(o.query1, (name,))
existing = cursor.fetchone()
if existing:
id, puuid = existing
return id, puuid
else:
return None, None
def create_database(conn, cursor):
cursor.execute('''
CREATE TABLE IF NOT EXISTS summoners (
id TEXT PRIMARY KEY,
puuid TEXT,
nickname TEXT
)
''')
conn.commit()
pass
## db 안닫고있음 id primary key라 중복되게 업데이트하면 안됨
def save_info(conn, cursor, summoner_info):
id = summoner_info['id']
puuid = summoner_info['puuid']
name = summoner_info['name']
cursor.execute('''
INSERT INTO summoners(
id,
puuid,
nickname
) VALUES (?, ?, ?)
''',(id, puuid, name))
conn.commit()
conn.close()
pass
from flask import render_template, request, Blueprint
from RiotApiProject.app.controller import controller as c
from RiotApiProject.app.env.URL import profile
# controller 각 엔드포인트의 요청에 대한 응답만 정의함.
# 변수명을 반드시 확인하도록
# 플라스크 동적 템플릿 생성방법에 대해 공부
# 초기화면 검색화면
# 렌더링 화면 데이터 출력 템플릿
bp = Blueprint('main',__name__, template_folder="../templates", url_prefix='/kr')
#url_prefix에 의해 endpoit시작은 고정. 그렇기에 충돌없이 실행한다.
@bp.route('/', methods=['POST', 'GET'])
def input():
if request.method == 'POST':
summoner_name = request.form['summoner_name']
match_type = request.form['match_type']
#소환사 프로필 정보
summoner_info, rank, summoner_puuid = c.search(summoner_name)
url = f"{profile}{summoner_info['profileIconId']}.png"
#랭크 경기 정보
match_data = c.ingame(summoner_puuid, match_type)
return render_template('index.html', summoner_info=summoner_info, rank=rank, url=url, match_data=match_data)
return render_template('input.html')
# 뷰를 나눴는데 방법은 간단했다. 함수리턴 템플릿을 인풋으로 해놓고
# 조건부 post에 대한 부분의 리턴 템플릿은 인덱스로 해놓으니 문제없이 작동한다.
#변수저장 리스트
Api = 'yout_api'
profile = 'https://ddragon.leagueoflegends.com/cdn/11.11.1/img/profileicon/'
summonerV4 = 'https://kr.api.riotgames.com/lol/summoner/v4/summoners/by-name/'
leagueV4 = 'https://kr.api.riotgames.com/lol/league/v4/entries/by-summoner/'
matchnumL = 'https://asia.api.riotgames.com/lol/match/v5/matches/by-puuid/'
matchL = 'https://asia.api.riotgames.com/lol/match/v5/matches/'
rank = {
"queue_type": "",
"tier": "",
"match": "",
"leaguepoints": "",
"wins": "",
"losses": ""
}
run
에서 실행에 관한 선언과, 뷰의 블루프린트를 불러와 실행시키고, view
의 input
함수를 실행시킨다.
view
에서는 controller
를 모듈로 사용해, 기본적인 POST 요청으로 수집한 데이터에 대해 API와 함께 정보를 서칭하여 가져온다. 수집된 데이터의 형식은 모두가 딕셔너리 형식으로 반환될 수 있도록 하였다.
controller
는 model
을 이어주는 통로로도 사용을 해야하기 때문에, model
의 database
에 관한 처리를 다룬다. 그래서 database
의 커넥션, 커서는 컨트롤러에서 담당하여 처리하고 table
생성과 데이터 삽입에 관한 처리만 모델에 정리해 놓았다. controller
를 하나의 연결다리로 사용해야 하는 점에서, 처리를 하는데 조금 시간이 걸렸다.
loop
는 반복문 폴더이다. 반복적으로 접근해야되는 로직의 경우, 일일이 입력하는데 번거로움이 있기 때문에 묶어냈다.
URL, dict, order
는 링크 변수와 저장해야하는 딕셔너리를 미리 선언한 변수를 담았다. 변수는 미리 처리하여 정리해두는 것이 코드를 보는데 있어 깔끔하기 때문에, 중복되는 변수들은 최대한 미리 정리하였다.
기본적으로 조금 씩 최적화가 필요
회원가입 기능과 게시판 기능 구현. 데이터에 관한 처리는 db로.
관련된 영상 추천
데이터 처리 속도 향상(로직의 접근을 달리 해야할듯)
데이터 UI 개선하여 가독성있도록 만들기
이미지 파일들도 로드하여 띄우기