์ง๋ ํฌ์คํ ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํ๋ ๊ฒ๊น์ง ํด๋ดค๋ค. ์ด๋ฒ ์๊ฐ์๋ ๊ตฌ๊ธ ์ฐจํธ๋ฅผ ์ด์ฉํด์ ๋ฐ์ดํฐ๋ฅผ ์๊ฐํํ๊ณ , ์๊ฐํ ํ์ด์ง๋ฅผ ๋ก๊ทธ์ธํ์ง ์๊ณ ๋ ๋ณด์ด์ง ์๋๋ก ํด๋ณด๋๋ก ํ๊ฒ ๋ค.
1. ํ์ด์ง ์ด๋ ๊ถํ ํ์ธํ๊ธฐ
๊ฐ์ฅ ๋จผ์ , ๋ก๊ทธ์ธํ์ง ์๊ณ ๋ ๋์๋ณด๋๋ฅผ ๋ชป๋ณด๊ฒ ํ๊ธฐ ์ํด์๋ ๋ก๊ทธ์ธ ์ ๋ณด๊ฐ ํ์ํ๋ค. ์ง๋ ํฌ์คํ ์์ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด access token์ ๋ฐ๊ธํ ๊ฒ์ ๊ธฐ์ตํ๊ณ ์์ ๊ฒ์ด๋ค. ์ด access token์ ์ด์ฉํด์ ํ์ฌ access token์ด ์ ํจํ์ง ํ์ธํ ํ ์ ํจํ๋ฉด ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , ์๋๋ฉด ๋ค์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ณด๋ด์ฃผ๋ฉด ๋๋ค.
์ด์ ์ฝ๊ฒ ๋งํ ๊ฑธ ๊ตฌํํด๋ณด์.
๋จผ์ , access token์ ์ฟ ํค ๋ฐ์ดํฐ์ ํฌํจ๋์ด ์๋ค. ajax jquery์์๋
$.cookie("access_token")
์ด๋ ๊ฒ ๋ฐ์์ฌ ์ ์๋ค. ์ด ๋ด์ฉ์ header์ ๋ฃ์ด์ ์๋ฒ ์ชฝ์ผ๋ก ๋ณด๋ด์ค์ผํ๋๋ฐ, ํ ํฐ์ด jwt ํน์ OAuth์ ๊ดํ ๋ด์ฉ์ด๋ผ๋ฉด ์์ Bearer๋ผ๋ ํ์ ์ ๋ฃ์ด์ฃผ์ด์ผํ๋ค. ์ ์ฒด Ajax ํจ์๋ฅผ ์ง๋ณด๋ฉด ์๋์ ๊ฐ๋ค. ์ด ํจ์๋ ๋์๋ณด๋ ๋ก๋ ์์ ์๋ํด์ผํ๋ฏ๋ก ๋น์ฐํ ๋์๋ณด๋ html ์์ ์ฝ์ ํ๋ค.
$(document).ready(function ($) {
$.ajax({
url: "<http://localhost:8000/me>",
method: "GET",
contentType: false,
processData: false,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Bearer" + $.cookie("access_token"), // token ์ ๋ฌ
},
success: function (response) {
console.log("get Succeed!");
},
error: function (response) {
alert(response.responseJSON.detail);
location.href = "<http://localhost:8000/>"; // ๊ถํ ์์ผ๋ฉด ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก
},
});
});
์ด์ "<http://localhost:8000/me>" ์ ๋ํ ์๋ฒ ์ชฝ ํจ์๋ฅผ ์ง๋ณด์. ๋ฌด๋ ค ์ธ ๊ฐ์ง ํจ์๊ฐ ๋ฌผ๋ ค ์์ผ๋ฏ๋ก ์กฐ์ฌํด์ ํ๋์ฉ ์ ๊ทผํด๋ณด๊ธฐ๋ก ํ๋ค.
์ด์ ์ get_current_user ๋ผ๋ ํจ์๋ฅผ ์์ฑํ์ผ๋, ์ด๊ฑธ ๋ฐํ์ผ๋ก ํ ๋ฒ ์ง๋ณด๋ ค๊ณ ํ๋ค. ์ฐ์ ์ด๋ป๊ฒ ๋์ํ์๋์ง ํ ๋ฒ ํ์ธํด๋ณด์.
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
get_current_user ํจ์๋ token์ ๋ฐ์์ user๋ฅผ ๋๋ ค์ฃผ๊ณ , token์ด ์ ํจํ์ง ์๊ฑฐ๋ ์ ์ ๊ฐ ์์ผ๋ฉด exception์ ์ผ์ผํค๋๋ก ์์ฑ๋์ด ์๋ค. token: str = Depends(get_token) ๋ถ๋ถ์ token์ get_token ํจ์์ ๋ฆฌํด๊ฐ์ผ๋ก ๊ฐ๊ฒ ๋ค๋ ๋ป์ด๋ get_token ํจ์๋ ํ์ธํด๋ณด์.
def get_token(authorization: str = Header(default=None)):
print(authorization)
return authorization[6:]
์ด ํจ์์์๋ token์ Header์์ ๋ฐ์์์ ๋ท์ฒ๋ฆฌ ํ์ ๋ฆฌํดํ๋ค. ๊ทธ๋ ๋ค๋ ๊ฒ์, ์ฐ๋ฆฌ๊ฐ Ajax ํจ์๋ก ์์ฑํ ๋ถ๋ถ์์
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: "Bearer" + $.cookie("access_token"), // token ์ ๋ฌ
},
์ด! ๋ถ๋ถ์ Authorization ์์ ๊ฐ์ ธ์จ๋ค๋ ๊ฒ์ด๋ค. ๊ทธ๋ผ ๋ ์ด์ ์ถ๊ฐ์ ์ผ๋ก ๊ตฌํํ ๋ถ๋ถ์ ์๋ค. ๋ค๋ง ์ด ์๊ด๊ด๊ณ๋ฅผ ์๊ณ ์์ด์ผ localhost:8000/me ์ ์ฒ๋ฆฌ ํจ์๋ฅผ ์ดํดํ ์ ์๋ค. ๊ทธ๋์ ์ด๊ฑธ ์ด๋ป๊ฒ ์์ฑํ ๊ฑฐ๋๋ฉด
# user info
@app.get('/me')
async def get_me(user: models.User = Depends(utils.get_current_user)):
return user
์ด๊ฒ ๋์ด๋ค. ์ฐจ๊ทผ์ฐจ๊ทผ ๋์ง์ด๋ณด๋ฉด ์ง๊ธ ์ ๋ ฅ์ผ๋ก user๊ฐ ๋ค์ด๊ฐ๋๋ฐ, get_current_user ์ ๋ฆฌํด ๊ฐ์ ๋ฐ๋ฅธ๋ค. get_current_user์ user๋ฅผ ์ป๊ธฐ ์ํด์ get_token์ ํ์์ ์ผ๋ก ๋ถ๋ฌ์ token์ ์ป์ด์ค๊ณ , ์ด token์? ์ฐ๋ฆฌ๊ฐ request์ ํจ๊ป ์ ์กํ header์ ์๋ค. ๊ทธ๋ฌ๋๊น ์ด๋ ๊ฒ๋ง ํด์ฃผ๋ฉด ๋์ด๋ค!
์ด์ ๊ตฌ๊ธ ์ฐจํธ๋ฅผ ๊ทธ๋ฆฌ๋ฌ ๊ฐ๋ณด์.
2. Google Chart ์ ์ฉํ๊ธฐ
๊ตฌ๊ธ ์ฐจํธ ๊ณต์๋ฌธ์๋ ์ฌ๊ธฐ ์๋ค.
Google ์ฐจํธ ์ฌ์ฉ | Charts | Google for Developers
๊ทธ ์ค์์๋ ์ด ๋ชจ๋ํฐ๋ง ์์คํ ์ ์ ํฉํ๊ฒ ๋ค ์ถ์ ๊ฒ์ด ์์ญ ์ฐจํธ๋ผ์, ๊ทธ๊ฑธ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ ํ๋ค. ์ด๋์ ๋ ์ฌ์ฉ๋ฒ์ ์ ๋๋ดํ๊ณ ๋ฐ์ดํฐ ํ์ ๋ง ๋ง์ถฐ์ฃผ๋ฉด ๋๋ฏ๋ก ๊ฐ์ ์๋ง๋ ์ฐจํธ๋ฅผ ์ ํํ๋ฉด ๋๊ฒ ๋ค.
์๊ฐํ: ์์ญ ์ฐจํธ | Charts | Google for Developers
head ๋ถ๋ถ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ์์ง ๋ง์.
<script
type="text/javascript"
src="https://www.gstatic.com/charts/loader.js"
></script>
์ฐจํธ๋ฅผ ๊ทธ๋ฆด ๋๋ option์ ์ค์ ํด์ผํ๋ค. ๊ทธ๋ฆด ๋ฐ์ดํฐ๊ฐ ํ๋๋ผ๋ฉด ์๊ด์์ง๋ง, ์๋๋ผ๋ฉด option์ ๋ฐ์ดํฐ๋ง๋ค ์ง์ฃผ๊ณ ๋์ค์ ์ฝ์ ํ๋ ๊ฒ์ด ํธํ๋ค(๊ฐ์ฒด์งํฅ์ ์ฅ์ ๋ฐฑ๋ถ ์ด๋ฆฌ๊ธฐ ํ๋ก์ ํธ).
A. PostgreSQL ๋ฐ์ดํฐ๋ฅผ FastAPI๋ก ๋ฐ์์์ ๊ตฌ๊ธ ์ฐจํธ ๊ทธ๋ฆฌ๊ธฐ
option์ ๋ค์ด๊ฐ์ผํ ๊ฒ์ ๋ฐ์ดํฐ, ๋ฐ์ดํฐ ์ด๋ฆ(์ฐจํธ ํ์ดํ), document ์๋ณ์, plotting option์ด๋ค. ์์๋ฅผ ํ ๋ฒ ๋ณด์.
var chartOption = function (target, color, name, measure) {
this.name = name; // ๋ฐ์ดํฐ ์ด๋ฆ(ํ ํ์ดํ)
this.target = target; // document ์๋ณ์
this.data = null; // ๋ฐ์ดํฐ
this.chart = null;
this.options = { // ์ฐจํธ plotting option
legend: { position: "none" },
colors: [color],
title: name,
titleTextStyle: {
fontSize: 17,
bold: true,
italic: true,
},
hAxis: { textPosition: "none" },
vAxis: { title: name + " " + measure },
};
};
์ด ํ๋ก์ ํธ์ ๊ฒฝ์ฐ์๋ ์ฐจํธ ์ต์ ๊ฐ์ฒด๋ ํจ์๋ก ๊ตฌ์ฑํ๋๋ฐ, ๋ฐ์ดํฐ๊ฐ ๋น์ฅ ์๋ ์ด์ ๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํด์ผํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ต์ ๊ฐ์ฒด๋ ์๋์ ๊ฐ์ด ๋ถ๋ ๋ค.
var voltage_option = new chartOption(
"voltage_chart", // document ํ๊ฒ ์๋ณ์
"#8ECAE6", // color
"voltage", // ์ด๋ฆ
"(V)" // measure(๋จ์)
);
๊ทธ๋ผ ์ด์ ์ฐจํธ๋ฅผ ๊ทธ๋ฆฌ๋ ํจ์๋ฅผ ๋ง๋ค์ด์ฃผ์ด์ผํ๋ค. ์ด ํจ์์์๋ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๊ณ , ์ด ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๊ธ ์ฐจํธ๊ฐ ์ํ๋ ๋ฐฉ์๋๋ก ์ ์ฌํด์ ์ฐจํธ๋ฅผ ์ ์ ํ ์์น(document ํ๊ฒ ์๋ณ์๊ฐ ๋๊ฒ ๋ค)์ ๊ทธ๋ฆฌ๊ธฐ ๊น์ง ํด์ค์ผํ๋ค. ์ฝ๋๋ก ๋ณด์.
function drawChart(option) {
var o = option;
var data = [];
if (o != null) {
if (o.chart == null && o.data == null) {
o.data = new google.visualization.DataTable();
o.data.addColumn("string", "time");
o.data.addColumn("number", o.name);
๊ตฌ๊ธ ์ฐจํธ๋ ๋ฐ์ดํฐ๋ฅผ DataTable์ด๋ผ๋ ๋ฐ์ดํฐ ํ์ ์ผ๋ก ์ ๋ฌํด์ฃผ๊ธฐ๋ฅผ ๋ฐ๋๋ค. pandas dataframe๊ณผ ๋น์ทํ ํ์์ด๋ค. ๋จผ์ ๊ฐ์์ ๋ฐ์ดํฐ์ ๋ง๋ column์ ์ ์ธํด์ค๋ค. ๋๋ ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ time๊ณผ data ๋ฑ ๋ column์ผ๋ก ํ์ฑ๋์ด ์์ผ๋ฏ๋ก ํ์ ์ ์๋ง๊ฒ ์ง์ ํด์ postgreSQL์ column๊ณผ ์ด๋ฆ์ ๋ง์ถฐ์ค๋ค(์ค์!)
$.ajax({
url: "<http://localhost:8000/api/data/>" + o.name,
method: "GET",
async: false,
success: function (response) {
var degrees = response;
if (degrees.length != 0) {
degrees.forEach(function (el, index) {
data.push([el["time"], el[o.name]]);
});
}
},
error: function (response) {
data.push(["", 0]);
},
});
๋ค์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๋ถ๋ถ์ด๋ค. ์ด๋ ์์์ ์ ๊ตฌํํด๋ ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ๊ฐ์ ธ์์ค๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ dataTable ํ์์ ๋ง๊ฒ time, ๊ทธ๋ฆฌ๊ณ data name์ ํ์ฉํด์ ํ๋์ฉ pushํด์ ์๋ก์ด data๋ฅผ ๋ง๋ค์ด์ค๋ค.
o.data.addRows(data);
o.chart = new google.visualization.AreaChart(
document.getElementById(o.target)
);
}
o.chart.draw(o.data, o.options);
}
๊ทธ๋ฆฌ๊ณ ๋ฏธ๋ฆฌ ์ ์ํด๋๋ dataTable์ data๋ฅผ row๋ก ์ถ๊ฐํด์ฃผ๊ณ , ๋ง์นจ๋ด ์ฐจํธ๋ฅผ ์ ์ธํ๋ค. target์ผ๋ก ์ง์ ํ id ์์ญ์ ์ฐจํธ๋ฅผ ๋ฃ์ด์ฃผ๊ณ , ๋ฐ์ดํฐ์ plotting option์ ๋ฃ์ด์ draw ํด์ฃผ๋ฉด ๋๋๋ค.
์ด์ ์์ฑํ ํจ์๋ฅผ ์ ์ฉํ๋๋ก ์ง๋ณด์.
var voltage_option = new chartOption( // ์ฐจํธ ์ต์
์ง์
"voltage_chart",
"#8ECAE6",
"voltage",
"(V)"
);
google.charts.load("current", { packages: ["corechart"] }); // ์ฐจํธ ๋ก๋ฉ
google.charts.setOnLoadCallback(function () { // load ๋๋ฉด draw
drawChart(voltage_option);
});
์ฐจํธ ์ต์ ์ ์ง์ ํ๊ณ , ๊ตฌ๊ธ ์ฐจํธ ๋ก๋ฉ์ ์ํด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋จผ์ ๋ก๋ฉํด์ค๋ค. ๊ทธ๋ฆฌ๊ณ callback ํจ์๋ฅผ ๋ถ๋ฌ์ ์ ์ํ draw chart ํจ์๋ฅผ ๋ถ๋ฌ์ฃผ๊ธฐ๋ง ํ๋ฉด ๋์ด๋ค.
์ด์ ๊ทธ๋ฆฌ๋ ๊ฑด ๋ค ๋๋ฌ๋ค!!!!!!!!!!!!!!!!!!!! ์ ๋ง ๋ง์ง๋ง์ผ๋ก ์ด ์์คํ ์ ๋ชจ๋ํฐ๋ง ์์คํ ์ด๊ธฐ ๋๋ฌธ์ ์๋ ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ์ด ํ์ํ๋ ๊ทธ๊ฒ๋ง ๊ตฌํํด๋ณด๋๋ก ํ์. ๊ฐ๋จํ๋ค!
B. ๊ทธ๋ํ ์ค์๊ฐ ์ ๋ฐ์ดํธ ํ๊ธฐ
์ด๋ฅผ ์ํด์๋ ๊ฐ๋จํ updateChart๋ผ๋ draw chart์ ์์ฃผ ์ ์ฌํ ํจ์ ํ๋๋ง ์์ผ๋ฉด ๋๋ค. ์ด์ ๊น์ง ์ ์ฌ๋์ง ์์ ๋ฐ์ดํฐ๋ง ๋ถ๋ฌ์์ ๋ค์ drawChart๋ฅผ ๋ถ๋ฌ์ฃผ๋ ๋์ด๋ค. ๊ตฌํ์ ๋ณด์.
function updateChart(option) {
var o = option;
// ํ ๋ฒ์ ๋ณด์ฌ์ฃผ๋ ๋ฐ์ดํฐ ์ ์ง์ - 15๊ฐ ๋์ด๊ฐ๋ฉด ์ญ์
o.data.removeRows(0, o.data.getNumberOfRows());
$.ajax({
url: "<http://localhost:8000/api/data/>" + o.name,
method: "GET",
async: false,
success: function (response) {
var degrees = response;
var data = [];
if (degrees.length != 0) {
degrees.forEach(function (el, index) {
data.push([el["time"], el[o.name]]);
});
}
console.log(data);
o.data.insertRows(o.data.getNumberOfRows(), data);
drawChart(o);
},
error: function (response) {
alert("Get data failed");
},
});
}
์ด ํจ์์์๋ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๋ค๊ฐ ๋ค์ ๋ถ๋ฌ์ค๋ ๋ฐฉ์์ผ๋ก ์ ๋ฐ์ดํธ๋ฅผ ์งํํ๋ค. ๋ ํจ๊ณผ์ ์ผ๋ก ์์ฑํ๋ ค๋ฉด ์ผ์ ๊ฐฏ์ ์ด์์ด ๋๋ฉด ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๊ณ ์ถ๊ฐ๋ ๋ฐ์ดํฐ๋ ์ธ๋ฑ์ค๋ฅผ ์ ์ญ๋ณ์๋ก ๊ด๋ฆฌํด์ append ํด์ค ์๋ ์๊ฒ ๋ค.
์ด๋ ๊ฒ ํจ์๋ฅผ ์์ฑํ์ผ๋ฉด ์ด์ interval์ ์ค์ ํด์ ์ผ์ ์๊ฐ๋ง๋ค ํจ์๋ฅผ ๋ถ๋ฅด๋๋ก ํด์ฃผ๋ฉด ๋๋ค.
setInterval(function () {
updateChart(voltage_option);
updateChart(energy_option);
updateChart(current_option);
updateChart(power_option);
updateChart(pf_option);
}, 10000);
์ด๋ ๊ฒ ๋๋ฉด 10์ด์ ํ ๋ฒ์ฉ ๋ฐ์ดํฐ๊ฐ ์ ๋ฐ์ดํธ ๋๋ค.
์ง์! ์ด๋ ๊ฒ ํ๋ฉด ๋์ด๋ค.
3. ๋ง๋ฌด๋ฆฌ
๋ชจ๋ํฐ๋ง ํ์ด์ง๋ฅผ ๋ง๋๋ ๊ฐ๋จํ ํ๋ก์ ํธ๋ ์ฌ๊ธฐ์ ๋ง๋ฌด๋ฆฌ ํ์๋ค. ๋ ๋ณต์กํ๊ฒ ๋๋ฒจ๋กญํ ์๋ ์์ง๋ง ์ง๊ธ ๋น์ฅ ํ์ํ ์์ค์ ์๋๊ณ , ๋์ค์ ๋ฆฌํฉํ ๋ง๋ง ์์๊ฒ ์ ํด๋๋ฉด ๋๊ณ ๋๊ณ ์ธ๋งํ ํ ํ๋ฆฟ์ด ๋ ๊ฒ ๊ฐ๋ค.
์ค์ ์ฐ์ ํ์ฅ์ ํฌ์ ๋๋๋ก ํ๋ ค๋ฉด ๊ด๋ฆฌ์ ํ์ด์ง๋ ์ผ์ ์ ์ด ํ์ด์ง ๋ฑ๋ ์์ด์ผํ๊ณ , ์ง๊ธ๋ณด๋ค ๋ณต์กํ๊ณ ๋์์ธ๋ UI๊ฐ ํ์ํ๊ฒ ์ง๋ง ๋น์ด ๋ชฉํํ๋ ์ค์๊ฐ ์ผ์ ๋ฐ์ดํฐ ๋ชจ๋ํฐ๋ง์ ๋ชฉ์ ์ ๋ฌ์ฑํ๋ค๊ณ ๋ณผ ์ ์๊ฒ ๋ค.
Fast API, PostgreSQL, Jqeury๋ฅผ ์ฌ์ฉํด์ ๋ง๋ ์ด ํ ํ๋ฆฟ์ ์์ ํด์ ์ถํ Elastic Search , Logstash, Kibana์ ๋ถ์ฌ Personal Home ํ๋ก์ ํธ๋ฅผ ์ด์ด๊ฐ๋ ค๊ณ ํ๋ค. ์ด ๋ด์ฉ์ ๋ค๋ฅธ ์นดํ ๊ณ ๋ฆฌ์์~!
์๊ฐํ๋ ๊ฒ๋ณด๋ค ๋ง์ ๋ถ๋ค์ด ์ด ์๋ฆฌ์ฆ๋ฅผ ์ฝ์ด์ฃผ์ จ๋๋ฐ, ๋ชจ๋๋ค ์ด ๋ฌธ์๊ฐ FastAPI ์ ๋ฌธ์ ๋์์ด ๋์ จ๊ธธ ๋ฐ๋๋๋ค.
์ฝ์ด์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค!