종강 후 할게 없던 대학생은 먹잇감을 하나 찾았다.
넥슨 open API가 나왔다!
막연히 서비스를 해보고 싶다는 생각이 있었는데, 이 API를 이용해서 하나 만들어보기로 했다.
일단 넥슨 게임중에서 가장 좋아하는 메이플스토리로 서비스를 만들기로 했다.
원래는 유저들의 평균 전투력,6차 스킬 강화 상태 등을 알려주는 것으로 구상했는데, 무언가 2% 부족했다.
프로토타입을 만들어 봤는데, 사이트에 들어가면 그냥 표만 주르륵 나오고 끝이었다.
사람들의 접속을 유도할 수 있고 피드백이 있는 무언가가 필요했다.
그래서 생각한 것이 닉네임을 입력하여 자신의 위치를 알 수 있게 하는 기능을 추가하는 것이다.
유일한 문제점은 내가 갓 수료한 1학년 따리라는 것이다.
웹도 모르고, 서버도 모르고, 사이트 여는 법도 모른다.
앞으로 아예 무지했던 사람이 구글링만으로 서비스를 만들었던 과정을 써보려고 한다.
API도 정확히 뭔 지 몰랐다.
막연히 어떤 정보를 쏴주는 것 이라고만 알고 있었다.
그래서 처음 배운 것은 API에서 정보를 받아오는 것이었다.
제일 익숙한 Python 샘플코드를 살펴봤다.
response라는 객체에 받은 API키, url을 넣어서 request하면 정보가 오는 것이다.
request는 크롤링을 해봤어서 익숙했는데, 웹의 정보를 가져올 수 있는 함수이다.
https://open.api.nexon.com/maplestory/v1/id?character_name=123
위 링크에 들어가보면 유효한 API 키를 입력하라고 나온다.
따라서 API로 정보를 얻으려면 url과, api키를 쏴줘야 한다.
API에 정보를 요청하면 json의 형태로 받을 수 있다.
json이란 파이썬의 배열과 딕셔너리를 합친 것이라고 할 수 있다.
{
"date": "2023-12-21T00:00+09:00",
"character_class": "제논",
"use_preset_no": "1",
"use_available_hyper_stat": 1518,
"hyper_stat_preset_1": [
{
"stat_type": "STR",
"stat_point": 7,
"stat_level": 3,
"stat_increase": "힘 90 증가"
},
{
"stat_type": "DEX",
"stat_point": 3,
"stat_level": 2,
"stat_increase": "민첩성 60 증가"
},
{
"stat_type": "INT",
"stat_point": null,
"stat_level": 0,
"stat_increase": null
}]
API로 받은 정보 중 일부를 가져와봤다.
response.json()에 위 정보가 담기게 된다.
response.json()[0]은 response 전체 (발췌 하면서 이후 내용이 잘렸다)
response.json()[0]["date"]는 2023-12-21T00:00+09:00를 가져온다.
hyper_stat_preset_1라는 키를 보면 값이 배열로 되어있는 것을 볼 수 있다.
배열안에 딕셔너리, 딕셔너리 안에 배열, 그 배열 안에 딕셔너리가 있다.(...)
처음에는 좀 헷갈려서 고생했지만 하나하나 프린트 해보면서 익힐 수 있었다.
DEX의 레벨을 가져오고 싶다면 respose.json()[0]["hyper_stat_preset_1][1]["stat_level"] 이렇게 쓰면 된다.
json의 형태는 API마다 다르므로 프린트 해보며 데이터를 살펴봐야 한다.
앞서 말한 대로 서비스를 위해 유저들의 전투력, 6차 강화 스킬 정보를 가져와야했다.
넥슨 API 사이트를 참고하여 다음과 같은 로직으로 구현했다.
파라미터로 날짜, 페이지 번호, 월드 타입을 보내면 페이지 번호에 맞는 캐릭터 200개를 얻을 수 있다.
def get_name(headers, today, i, weight):
params = {"date":today, "page":i, "world_type":0}
response = requests.get("https://open.api.nexon.com/maplestory/v1/ranking/overall", headers = headers, params = params)
nameList = []
randList = [0 for j in range(weight)]
for index in range(weight):
randList[index] = random.randint(0,199)
for index in randList:
nameList.append(response.json()["ranking"][index]["character_name"])
return nameList
get_name이라는 함수를 만들었다. (파이썬)
파라미터로 headers, today, i, weight를 받는다.
headers는 내 API 키 정보,
date는 오늘 날짜, (datetime모듈을 이용해 구할 수 있다.)
i는 검색할 페이지 번호,
weight는 200 캐릭터 중 랜덤으로 몇 개의 캐릭터를 뽑을 건지 정하는 가중치이다.
처음에는 생각없이 모든 캐릭터의 평균을 내려고 했지만 양이 너무 많아 가중치를 추가하여 표본의 수를 조절할 수 있도록 했다.
i를 파라미터로 받아 i번쨰 페이지의 캐릭터 정보를 weight개 만큼 가져와 리턴한다.
파라미터로 캐릭터 닉네임을 보내 ocid를 얻을 수 있다.
ocid란 캐릭터마다 부여된 식별자로, 유일하다.
def get_ocid(headers, name):
params = {"character_name":name}
response = requests.get("https://open.api.nexon.com/maplestory/v1/id", headers = headers, params = params)
if "error" in response.json():
return -1
else:
ocid = response.json()["ocid"]
return ocid
get_ocid라는 함수를 만들었다.
파라미터로 name을 받아 ocid를 리턴한다.
API 오류가 발생할 시 -1을 리턴하여 예외처리한다.
ocid와 날짜를 받아 기본 정보를 반환한다.
def get_level(headers, ocid, today):
if ocid == -1:
return 300
params = {"ocid":ocid, "date":today}
response = requests.get("https://open.api.nexon.com/maplestory/v1/character/basic", headers = headers, params = params)
if response.json()["character_level"] == None:
return 300
return response.json()["character_level"]
기본 정보 중 레벨 정보만 필요하여 get_level 함수를 작성했다.
캐릭터 레벨을 반환하지만 마찬가지로 API오류가 나면 300을 반환하여 예외처리한다.
ocid와 날짜를 받아 종합 능력치 정보를 반환한다.
def get_stat(headers, ocid, today):
params = {"ocid":ocid, "date":today}
if ocid == -1:
return -1
response = requests.get("https://open.api.nexon.com/maplestory/v1/character/stat", headers = headers, params = params)
if response.json()["character_class"] == None:
return -1
elif int(response.json()["final_stat"][28]["stat_value"]) < 50:
return int(response.json()["final_stat"][42]["stat_value"])
else:
return -1
로직은 비슷하고 예외처리는 -1로 했다.
elif 조건은 전투력 표본에서 본템을 낀 표본만 가져오기 위해 사냥 세팅인 캐릭터는 제외하는 로직이다.
앞에서 만든 함수들로 데이터들을 수집한다.
white_sheet 함수를 마지막에 작성하여, 주먹구구식으로 추가한 기능들이 많다.
def write_sheet(headers, today, i, start_level, cur, conn):
print(start_level,"collecting data...")
statList = []
index = 0
count = 1
level = 300
weight = {285:140, 280:12, 275:6, 270:4,260:1}
weight_i = {285:1, 280:1, 275:1, 270:1,260:1}
stat_cut = {285:40000000, 280:30000000, 275: 20000000, 270:10000000,260:5000000}
while(level > start_level):
print(count)
count += 1
ocidList = []
nameList = get_name(headers, today, i, weight[start_level])
for name in nameList:
ocid = get_ocid(headers, name)
stat = get_stat(headers, ocid, today)
if stat != -1 and stat > stat_cut[start_level]:
statList.append(stat)
level = get_level(headers, ocid, today)
i = i+weight_i[start_level]
sum = 0
leng = len(statList)
statList.sort()
statList.reverse()
print("size:", leng)
print(start_level,"writing data...")
for power in statList:
if power != -1:
index += 1
cur.execute("SELECT stat_detail_%d FROM stat_detail WHERE id = %d;" %(start_level, index))
result = cur.fetchall()
try:
x = result[0]
cur.execute("UPDATE stat_detail SET stat_detail_%d = %d WHERE id = %d;"
%(start_level, power, index))
except:
cur.execute("INSERT INTO stat_detail (id, stat_detail_%d) VALUES(%d, %d);"
%(start_level, index, power))
sum = sum + (power)
cur.execute("INSERT INTO stat_average (average_stat, leng) VALUES (%d, %d);" %(sum / leng, leng))
conn.commit()
print(start_level,"end", leng)
return i
write_sheet 함수는 headers, today, i, start_level, cur, conn을 파라미터로 받는다.
(cur, conn은 추후 설명)
각종 변수들을 초기값 설정해주고,
검색 캐릭터들의 level이 start_level보다 작을 때 까지 아래 과정을 반복한다.
1. i 페이지 번호의 nameList 생성 (get_name 함수)
2. nameList 요소들의 ocid 얻기 (get_ocid 함수)
3. 얻은 ocid를 통해 stat값 얻기 (get_stat 함수)
4. stat_cut에 따라 조건에 안맞는 표본 제거
5. level값 업데이트
6. i 값 i_weight에 따라 업데이트
weight와 i_weight는 표본 수 조절을 위해 조정하는 값들이다.
(테스트 시에는 속도를 위해 표본 수를 줄일 수 있다)
while문을 벗어나면 statList를 정렬하고, DB에 각종 값을 작성한다. (DB 추후 설명)
여기서 return값이 i인데, 이는 다음과 같이 코드를 작성하기 위함이다.
i = 1
i = write_sheet(headers, today, i, 285, cur, conn)
i = write_sheet(headers, today, i, 280, cur, conn)
i = write_sheet(headers, today, i, 275, cur, conn)
i = write_sheet(headers, today, i, 270, cur, conn)
i = write_sheet(headers, today, i, 260, cur, conn)
함수를 모두 작성하고, 다음과 같이 코드를 쓰면 레벨 구간에 맞춰 데이터를 얻을 수 있다.
페이지 번호인 i가 계속 이어지므로 랭킹정보를 모두 훑을 수 있다.
레벨이 285~300, 280~284, 275~279, 270~274, 260~269인 캐릭터들의 정보를 얻을 수 있다.
stat이 아닌 6차 강화, 레벨, 유니온의 정보를 얻는 부분도 비슷한 로직이다. (빠른 DB 업데이트를 위해 파일 분리함)
유저 데이터를 저장 할 데이터베이스가 필요했다.
간단하게 스프레드시트를 이용해보려 했지만 초당 호출 횟수 제한이 있고, 여러모로 제약이 많았다.
결국 1도 모르는 데이터베이스를 배우기로 했다.
가장 대중적인 DB인 것 같아 선택했다.
나무위키에선 다음과 같이 설명한다.
보통 mysql이라는 오픈소스를 이용해 다룬다고 한다.
mysql은 다음과 같은 방식으로 작동한다.
DB마다 접근 가능한 id와 pw존재
(id와 pw를 알더라도 접근 가능 ip를 설정해 보안을 강화할 수 있다)
DB는 아래 형태로 존재한다.
Schema: DB의 전체적인 형태, 구조를 정의한다.
Table: Schema 아래 존재하며 데이터들이 실제로 저장되는 곳이다.
Table은 column, row로 정의되며 column은 데이터의 자료형과 각종 설정들을 정의한다.
Query를 이용하여 데이터를 다룬다.
기본적인 문법은 다음과 같다.
SELECT * FROM table_name;
table_name의 데이터를 모두 가져온다.
SELECT column_name FROM table_name;
table_name의 column_name 열의 대이터를 가져온다.
SELECT column_name FROM table_name WHERE CONDITION;
CONDITION에 맞는 column_name열의 값을 가져온다. (id = 3, price > 100000 등등)
INSERT INTO table_name (column1, column2 ...) VALUES(a, b ...)
table_name에 열과 대응되는 값을 삽입한다.
UPDATE table_name SET column_name = x WHERE CONDITION;
CONDITION에 맞는 column_name열의 값을 x로 업데이트 한다.
TRUNCATE TABLE table_name;
table_name 테이블의 값을 모두 삭제한다. (column 남아있음)
DROP TABLE table_name;
table_name 테이블을 삭제한다. (table 전체 삭제)
파이썬에 mysql을 다룰 수 있게 하는 모듈이 있어 이를 사용했다. (pymysql)
pymysql은 다음과 같이 사용한다.
# DB 접속정보 입력
conn = pymysql.connect(
host='xxx.xxx.xx.x',
user='xxxx',
password='xxxx',
db='xxxx',
charset='utf8'
)
# cur 객체를 커서로 지정
cur = conn.cursor()
# execute 함수로 Query 입력
cur.execute("TRUNCATE TABLE table_name;")
# 변동사항 저장
conn.commit()
# 연결 끊기
conn.close()
위 파이썬 코드를 보면 알 수 있듯 서식 지정자로도 Query를 실행할 수 있다.
cur.execute("INSERT INTO stat_average (average_stat, leng) VALUES (%d, %d);" %(sum / leng, leng))
최종적인 DB는 다음과 같이 되었다.
HTML은 마크업 언어로, 웹페이지를 구성한다.
태그를 이용하여 정보를 표현할 수 있다.
프로젝트에서 쓴 태그들만 정리해보자.
<head> </head>
웹페이지에서 보이지 않는 정보들을 처리하는 부분이다.
script, meta, style, title 태그 등을 포함한다.
<script> </script>
HTML에서 JS를 돌릴 수 있게 해준다.
사용법으로는 모든 스크립트를 태그 안에 넣는 방법과, 파일을 나눠 포함시키는 방법이 있다.
첫 번째 방법
<script>
var x = 1;
console.log(x);
</script>
두 번째 방법
<script src = "file_name.js"> </script>
<meta> </meta>
검색 엔진 노출을 위한 Keyword, 문자열 인코딩 등을 포함한다.
<style>
h2 {
line-height: 50px;
font-size: 25px;
}
</style>
HTML요소들의 디자인을 결정한다. css파일과 연계할 수 있다.
<title> TITLE </title>
웹페이지의 제목을 정의한다.
<body> </body>
웹페이지의 본 내용을 포함한다.
<h1> content </h1>
정해져있는 크기의 글자를 출력한다.
사진에 보이는 기능과 비슷하다.
<div> </div>
division의 약자로, 이름처럼 그냥 구역을 나누는 역할을 한다.
HTML의 가독성을 높이거나, css와 연계하여 구역의 스타일을 지정할 수 있다.
<ul class = "fruit">
<li> apple </li>
<li> banana </li>
<li> melon </li>
</ul>
ul태그는 순서없는 리스트를 의미하고, li로 각각의 요소를 정의할 수 있다.
script 태그에서 다음과 같이 이용할 수 있다.
const fruitList = document.querySelectorAll('.fruit li');
<a href = "URL or Id"> 바로가기 </a>
a태그는 URL이나, 같은 HTML에서 정의된 Id 요소를 연결해준다.
바로가기 텍스트를 클릭하면 href 속성값에 있는 URL이나 Id로 이동하게 된다.
<form id="name-form">
<input type="text" id="nick" name="name" placeholder="닉네임을 입력해주세요." />
<input type="submit" value="확인">
</form>
form 태그는 입력된 값을 다른 HTML파일이나 JS파일로 전송할 수 있다.
document.getElementById("name-form").onsubmit = function () {
const characterName = nick["value"];
}
script 태그 내에서는 위와 같은 코드를 사용하여 submit 버튼을 누르면 함수가 작동되도록 할 수 있다.
<select id="server" form="guild_form">
<option value="스카니아">스카니아</option>
<option value="루나">루나</option>
<option value="엘리시움">엘리시움</option>
<option value="크로아">크로아</option>
<option value="베라">베라</option>
<option value="오로라">오로라</option>
<option value="이노시스">이노시스</option>
<option value="유니온">유니온</option>
<option value="제니스">제니스</option>
<option value="레드">레드</option>
<option value="아케인">아케인</option>
<option value="노바">노바</option>
</select>
select 태그는 유저에게 선택 옵션을 제공한다.
input 태그는 텍스트, 버튼 등 타입을 지정할 수 있으며 안에 들어있는 값을 전달할 수 있다.
<canvas id="stat-detail-260-chart" , width="1800px" , height="500px"></canvas>
canvas 태그는 그림이나 그래프 등 시각 자료를 담는 태그이다.
chart.js 모듈을 사용하여 그래프를 쉽게 제작할 수 있다.
<table style = "margin: 10px auto;" id = "table">
<tr>
<td>닉네임</td>
<td colspan="2"id="nickname"></td>
<td>직업</td>
<td colspan="2" id="class"></td>
<td>날짜</td>
<td colspan="2" id="date"></td>
</tr>
<tr>
<td>영역</td>
<td colspan="2">레벨</td>
<td colspan="2">전투력</td>
<td colspan="2">헥사 강화</td>
<td colspan="2">유니온</td>
</tr>
<tr>
<td>점수</td>
<td colspan="2" id="levelscore"></td>
<td colspan="2" id="statscore"></td>
<td colspan="2" id="hexascore"></td>
<td colspan="2" id="unionscore"></td>
</tr>
<tr>
<td>백분위</td>
<td colspan="2" id="levelpercentile"></td>
<td colspan="2" id="statpercentile"></td>
<td colspan="2" id="hexapercentile"></td>
<td colspan="2" id="unionpercentile"></td>
</tr>
<tr>
<td>등급</td>
<td colspan="2" id="levelRating"></td>
<td colspan="2" id="statRating"></td>
<td colspan="2" id="hexaRating"></td>
<td colspan="2" id="unionRating"></td>
</tr>
<tr>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
<td style = "border: 0px;"></td>
</tr>
</table>
table 태그는 표를 작성할 수 있다.
마지막 행에 빈 줄이 있는 이유는 colspan 사용을 위해 열 개수를 맞추기 위함이다.
자바스크립트도 처음 배웠다.
변수 부분은 파이썬이고 로직은 C같았다.
기본적인 문법은 비슷해서 수월했지만 비동기 함수 부분에서 애를 먹었다.
비동기란 어떤 시간이 걸리는 로직에서 코드 순서를 제어하는 것이다.
예를들어 API를 가져오는데 1초가 걸린다면 그 1초를 기다린 다음에 다음 코드를 진행하는 것이다.
이를 구현하는 방법에는 3가지 정도가 있다. (callback, Promise, async/await)
사실 모든 방법이 원리는 같지만 개발자의 편의를 위해 문법만 조금 다른 것이다.
API 정보를 받아오는 Fetch 함수는 기본적으로 비동기 함수이다.
fetch("https://xxxxxxx.com")
.then((response) => response.json())
.then((data) => console.log(data))
fetch에서 정보를 가져오기까지 기다리고, then에서 다음 작업을 이어간다.
이러한 로직을 callback이라 한다. (어떤 함수가 끝나면 다음 함수를 실행, 파라미터로 넘김)
가장 간단한 방법이지만 남용할 경우 callback 지옥에 빠진다.
(callback hell)
난 callback, async/await 두 가지 방법으로 코드를 짰는데, 두 코드를 비교해보면 확실히 차이가 난다.
asynk/await는 이러한 callback hell을 해결할 수 있는 방법이다.
const get_member = async (oguild_id) => {
const urlString = "https://open.api.nexon.com/maplestory/v1/guild/basic?oguild_id=" + oguild_id + "&date=" + day;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
return data;
};
이와같이 async 형태의 함수를 만들고, fetch앞에 await를 붙이면 알아서 fetch가 될 때 까지 기다리고, 다음 코드가 실행된다.
내가 짠 비슷한 기능을 하는 코드로 비교해보자. (너무 긴 로직은 주석처리)
document.getElementById("name-form").onsubmit = function () {
// 변수 선언
urlString = "https://open.api.nexon.com/maplestory/v1/id?character_name=" + characterName;
fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } })
.then(response => response.json())
.then(data => {
ocid = data["ocid"];
urlString = "https://open.api.nexon.com/maplestory/v1/character/stat?ocid=" + ocid + "&date=" + day;
fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } })
.then(response => response.json())
.then(data => {
var mystat = Number(data["final_stat"][42]["stat_value"]);
urlString = "https://open.api.nexon.com/maplestory/v1/character/hexamatrix?ocid=" + ocid + "&date=" + day;
fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } })
.then(response => response.json())
.then(data => {
// 데이터 가공
urlString = "https://open.api.nexon.com/maplestory/v1/character/basic?ocid=" + ocid + "&date=" + day;
fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } })
.then(response => response.json())
.then(data => {
// 데이터 가공
urlString = "https://open.api.nexon.com/maplestory/v1/user/union?ocid=" + ocid + "&date=" + day;
fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } })
.then(response => response.json())
.then(data => {
//데이터 작성 및 에러처리
})
})
})
})
})
return false;
}
로직을 전부 주석처리했음에도 매우 복잡해보인다.
참고로 어떤 흐름인지 써보자면
닉네임 정보 > ocid API 호출 > 캐릭터 스탯 API 호출 > 캐릭터 6차 API 호출 > 캐릭터 레벨 API 호출 > 캐릭터 유니온 API 호출이다.
이제 async/await로 짠 코드를 보자.
document.getElementById("guild-form").onsubmit = function () {
//변수 선언
const get_ocid = async (name) => {
const urlString = "https://open.api.nexon.com/maplestory/v1/id?character_name=" + name;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
return data["ocid"]
}
const get_stat = async (ocid) => {
const urlString = "https://open.api.nexon.com/maplestory/v1/character/stat?ocid=" + ocid + "&date=" + day;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
return Number(data["final_stat"][42]["stat_value"]);
}
const get_level = async (ocid) => {
urlString = "https://open.api.nexon.com/maplestory/v1/character/basic?ocid=" + ocid + "&date=" + day;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
return Number(data["character_level"]);
}
const get_hexa = async (ocid) => {
urlString = "https://open.api.nexon.com/maplestory/v1/character/hexamatrix?ocid=" + ocid + "&date=" + day;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
//데이터 가공
return hexa
}
const get_union = async (ocid) => {
urlString = "https://open.api.nexon.com/maplestory/v1/user/union?ocid=" + ocid + "&date=" + day;
const response = await fetch(urlString, { headers: { "x-nxopen-api-key": API_KEY } });
const data = await response.json();
return Number(data["union_level"])
}
const get_average = async () => {
//데이터 작성 및 에러처리
}
get_average()
return false
}
훨씬 읽기 쉬워진다. (주석 처리를 안하면 더욱 차이가 심하다)
이 코드의 흐름은 위의 코드와 똑같다. (길드 관련 로직이라 길드 관련은 비교를 위해 삭제)
참고로 여러개의 async 함수를 연결하는 방법은 다음과 같다.
const get_members = async (ocid) => {...}
const get_ocid = async (ocid) => {...}
const get_level = async (ocid) => {...}
const get_hexa = async (ocid) => {...}
const get_union = async (ocid) => {...}
이와 같이 여러 async 함수를 만들어놓고, (return 값을 이용할 데이터로 한다)
const get_average = async () => {
member = await get_members();
for(i = 0; i < member.length; i = i+1) {
ocid = await get_ocid(member[i])
stat = await get_stat(ocid)
level = await get_level(ocid)
hexa = await get_hexa(ocid)
union = await get_union(ocid)
}
//데이터 처리
}
최종적으로 처리하는 async 함수를 하나 만들어 연결한다.
php는 js와 비슷한 웹 스크립트 언어이다.
php를 이용한 이유는 DB에서 데이터를 가져오기 수월하기 때문이다.
js에서 DB에 접근하려면 서버를 만들고, API도 만들어야한다.
하지만 "닷홈" 웹호스팅에서는 php를 이용해 코드 몇줄로 DB데이터를 가져올 수 있다.
php를 쓰는 방법은 파일확장자를 html에서 php로 바꾸고, (php 확장자에서 html은 정상 작동한다.)
head 태그 내에 php 태그를 작성하면 된다.
<?php
$conn = mysqli_connect(
'host',
'db id',
'db pw!',
'db user');
$result = mysqli_query($conn, "select * from hexa_average");
$hexa_average = array();
while ($r = mysqli_fetch_assoc($result)) {
$hexa_average[] = $r;
}
$result = mysqli_query($conn, "select * from hexa_detail");
$hexa_detail = array();
while ($r = mysqli_fetch_assoc($result)) {
$hexa_detail[] = $r;
}
$result = mysqli_query($conn, "select * from stat_average");
$stat_average = array();
while ($r = mysqli_fetch_assoc($result)) {
$stat_average[] = $r;
}
$result = mysqli_query($conn, "select * from stat_detail");
$stat_detail = array();
while ($r = mysqli_fetch_assoc($result)) {
$stat_detail[] = $r;
}
$levelrange = array();
for ($i = 52; $i < 60; $i = $i + 1) {
$sql = "select count(level) from level where level >= " . (string) (5 * $i) . " and level < " . (string) (5 * ($i + 1));
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$levelrange[] = $r;
}
}
$result = mysqli_query($conn, "select level from level");
$level_detail = array();
while ($r = mysqli_fetch_assoc($result)) {
$level_detail[] = $r;
}
$result = mysqli_query($conn, "select unionlevel from unionlevel");
$union_detail = array();
while ($r = mysqli_fetch_assoc($result)) {
$union_detail[] = $r;
}
$unionrange = array();
for ($i = 8; $i < 22; $i = $i + 1) {
$sql = "select count(unionlevel) from unionlevel where unionlevel >= " . (string) (500 * $i) . " and unionlevel < " . (string) (500 * ($i + 1));
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$unionrange[] = $r;
}
}
$stat_range_285 = array();
for ($i = 0; $i < 20; $i = $i + 1) {
$range = 50000000 * $i;
$sql = "select count(stat_detail_285) from stat_detail where stat_detail_285 > " . (string) ($range) . " and stat_detail_285 < " . (string) ($range + 50000000);
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$stat_range_285[] = $r;
}
}
$stat_range_280 = array();
for ($i = 0; $i < 20; $i = $i + 1) {
$range = 25000000 * $i;
$sql = "select count(stat_detail_280) from stat_detail where stat_detail_280 > " . (string) ($range) . " and stat_detail_280 < " . (string) ($range + 25000000);
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$stat_range_280[] = $r;
}
}
$stat_range_275 = array();
for ($i = 0; $i < 20; $i = $i + 1) {
$range = 20000000 * $i;
$sql = "select count(stat_detail_275) from stat_detail where stat_detail_275 > " . (string) ($range) . " and stat_detail_275 < " . (string) ($range + 20000000);
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$stat_range_275[] = $r;
}
}
$stat_range_270 = array();
for ($i = 0; $i < 20; $i = $i + 1) {
$range = 10000000 * $i;
$sql = "select count(stat_detail_270) from stat_detail where stat_detail_270 > " . (string) ($range) . " and stat_detail_270 < " . (string) ($range + 10000000);
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$stat_range_270[] = $r;
}
}
$stat_range_260 = array();
for ($i = 0; $i < 20; $i = $i + 1) {
$range = 5000000 * $i;
$sql = "select count(stat_detail_260) from stat_detail where stat_detail_260 > " . (string) ($range) . " and stat_detail_260 < " . (string) ($range + 5000000);
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$stat_range_260[] = $r;
}
}
$genesis_rate = array();
$sql = "select * from genesis";
$result = mysqli_query($conn, $sql);
while ($r = mysqli_fetch_assoc($result)) {
$genesis_rate[] = $r;
}
?>
사용한 php 코드 전문이다. php는 너무 낯설어서 그냥 구글 검색으로 코드를 긁어왔다.
js에서 다음과 같이 쓰면 php 변수를 가져올 수 있다.
var stat_average = <?= json_encode($stat_average) ?>;
var hexa_average = <?= json_encode($hexa_average) ?>;
var stat_detail = <?= json_encode($stat_detail) ?>;
var hexa_detail = <?= json_encode($hexa_detail) ?>;
사이트 링크: http://mapleaverage.dothome.co.kr
꽤 성공적이라고 생각한다.
총 2번의 업데이트가 있었다.
배포 당시: 본섭 정보만 제공
1차 업데이트: 리부트 정보 제공 및 간단한 정보 추가
2차 업데이트: 길드 , 레벨, 유니온 정보 제공 및 성적표 제공
사이트 배포 및 홍보: 12/27 (홍보글 조회수 약 3만)
1차 업데이트 및 작은 홍보: 12/29 (홍보글 조회수 약 6천)
12/28~12/31 동안 웹호스팅 기본 트래픽 15GB에 거의 근접하였다.
2차 업데이트 및 홍보: 1/7 (홍보글 조회수 약 1.7만)
1/9 오후 5시에 기본 트래픽 15GB에 거의 근접하였다.
12월에는 홍보글 작성 다음날부터 급격하게 유저가 떨어졌지만 1월 홍보글 이후에는 그렇게 많이 떨어지진 않았다.
내 생각에는 3가지 정도로 추릴 수 있을 것 같다.
구글 최상단에 노출되기 시작함
구글에 사이트 이름을 검색하면 12월에는 홍보글이 최상단에 떴지만 이제는 사이트 자체가 최상단에 노출된다.
배포 당시보다 컨텐츠 다양
배포 당시에는 캐릭터의 스펙과 상위% 정보만 제공하였다.
이것도 물론 사람들을 끌어당기긴 했지만 2차 업데이트에는 적은 홍보글 조회수에 불구하고 많은 사람들이 오게 되었다.
내 생각에는 "성적표 발급" 기능이 매력적이었다고 생각한다.
기존 % 정보는 사람들간 비교가 직관적이지 않아 한 번 보면 오 그렇구나 하고 끝이었다.
그러나 1월 업데이트 이후 성적표 기능은 친숙한 수능 등급 척도를 이용해 사람들간 비교를 가능하게 했고 많은 사람들이 접근하도록 만들었다.
타 커뮤니티로 공유
사이트를 메이플 인벤 사이트에만 홍보했지만 2차 홍보글에선 1차 홍보글보다 다른 커뮤니티에 퍼지게 되었다. 이는 2번 이유와 연관이 있다고 생각한다.
방금 추가 트래픽 긁었다.. (제일 작은걸 100GB로 해둔거 상술 아니냐..)