<step 4 비디오 재생하기>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>클론코딩 - step4[비디오 재생하기]</title>
<link rel="stylesheet" href="./youtube.css" />
<link
rel="shortcut icon"
href="../../../images/youtube/logo.ico"
type="image/x-icon"
/>
<script defer src="./youtube.js"></script>
</head>
<body>
<h3>유튜브 클론코딩 실습 4</h3>
<header class="header">
<div class="logo">
<img src="../../../images/youtube/logo.png" alt="logo" />
<h1 class="headerTitle">Youtube</h1>
</div>
<input
class="input"
type="search"
name="keyword"
id="keyword"
placeholder="Search"
/>
<button class="btnSearch">
<img
class="imgSearch"
src="../../../images/youtube/search.png"
alt="search"
/>
</button>
</header>
<div id="root"></div>
</body>
</html>
const keyword = document.querySelector("#keyword");
// input textfield에 입력한 후 엔터쳤을 때를 잡아내는 함수
keyword.addEventListener("keypress", (event) => {
console.log("keypress => " + event.keyCode);
// 사용자가 입력한 검색어를 찾는 함수 호출
if (event.keyCode == 13) {
handleSearch();
}
});
// 사용자가 입력한 검색어 읽어오기
const handleSearch = () => {
const user = document.querySelector("#keyword").value;
console.log("사용자가 입력한 검색어 => " + user);
search(user);
};
// video로 받아와서 구조분해할 수 있나? -> 리액트에선 가능, 바닐라에서는 불가
let videoItem;
const videoShow = (id, title, description, channelTitle) => {
videoItem = `
<section class = "detail">
<iframe
id="player"
type="text/html"
width="800"
height="480"
src="http://www.youtube.com/embed/${id}"
frameborder="0"></iframe>
<h2>${title}</h2>
<h3>${channelTitle}</h3>
<pre class = "description">${description}</pre>
<div>
<a href="./youtube.html">이전페이지</a>
<a href="javascript:search('손흥민')">이전페이지2</a>
</div>
</section>
`;
document.querySelector("#root").innerHTML = videoItem;
};
// 사용자가 입력한 검색어로 조건 검색 구현하기
const search = (query) => {
console.log("사용자가 입력한 btns 받음 => " + query);
const ajax = new XMLHttpRequest();
const videoList = [];
const key = "";
const SEARCH_URL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&maxResults=25&q=${query}&key=${key}`;
// const SEARCH_URL = `http://192.168.10.47:5500/google/youtube/step4/sample.json`;
// const SEARCH_URL = `http://192.168.10.71:5500/google/youtube/step4/sample.json`;
ajax.open("GET", SEARCH_URL, false);
ajax.send();
const most = JSON.parse(ajax.response);
const items = most.items;
let items2;
items2 = most.items.map((item) => ({
...item,
id: item.id.videoId,
}));
console.log(items2);
videoList.push(`<ul class = "videos">`);
items2.map((video) => {
// 자바스크립트에서는 파라미터로 값을 넘길 때 반드시 싱글쿼테이션' 또는 더블 쿼테이션"을 붙여야 값으로 인정된다
// 만약 붙이지 않으면 XXX is not defined라고 뜸
// 비디오 제목, 설명의 경우 특수문자(괄호 등) 배제하는 정규식 이용
const reg = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
const title = video.snippet.title.replace(reg, "");
const description = video.snippet.description.replace(reg, "");
videoList.push(
`<li class="container" onclick="videoShow('${video.id}','${title}','${description}','${video.snippet.channelTitle}')">`
);
videoList.push(`<div class="video">`);
videoList.push(
`<img class = "thumbnail" src='${video.snippet.thumbnails.medium.url}' />`
);
videoList.push(`<div">`);
videoList.push(`<p class = "title">${video.snippet.title}</p>`);
videoList.push(
`<p class = "channelTitle">${video.snippet.channelTitle}</p>`
);
videoList.push(`</div">`);
videoList.push(`</div">`);
videoList.push(`</li">`);
}); // end of map
videoList.push(`</ul>`);
document.querySelector("#root").innerHTML = videoList.join("");
}; // end of search
* {
box-sizing: border-box;
/* border: 1px solid red; */
}
.header {
display: flex;
height: 4rem;
padding: 0.5rem 1rem;
margin: 0 0.2rem;
background-color: black;
}
.logo {
display: flex;
align-items: center;
margin-right: 1em;
}
.headerTitle {
color: white;
margin: 0.2rem 0.5rem;
}
.input {
flex-basis: 100%;
font-size: 1.1rem;
outline: 0;
margin: 0rem 0.5rem;
}
.btnSearch {
background-color: rgb(223, 50, 50);
outline: 0;
}
.imgSearch {
height: 100%;
padding: 0.5em 0.2em;
}
.videos {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
list-style: none;
padding-left: 0px;
margin: 0;
gap: 14px;
}
.container {
width: 100%;
padding: 0.2em;
}
.video {
width: 100%;
height: 100%;
display: flex;
align-items: center;
border: 1px solid lightgray;
box-shadow: 3px 3px 5px 0px rgb(185, 185, 185);
transition: transform 250ms ease-in;
cursor: pointer;
}
.video:hover {
transform: scale(1.02);
}
.thumbnail {
width: 40%;
height: 100%;
}
.title,
.channelTitle {
margin: 10px;
font-size: 0.8rem;
}
.channelTitle {
font-size: 0.6rem;
}
.detail {
padding: 1em;
}
.description {
white-space: pre-wrap;
}
<JSP 사용, index 테이블 메인 바꾸기>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String menu = request.getParameter("menu"); // info or login or board or google
out.print(menu);
%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>패턴1</title>
<style>
a {
text-decoration: none;
}
</style>
</head>
<body>
<!-- 태그는 중첩해서 사용 가능함 -->
<!-- 크기는 %로 주는 것이 좋다(가변적이니까) -->
<table border="1" align="center" width="1000px">
<tr>
<td width="100%" height="50px">
<!-- top 시작 -->
<%@ include file="top.jsp"%>
<!-- top 끝 -->
</td>
</tr>
<tr>
<td>
<!-- body 시작 -->
<table>
<tr>
<!-- 메뉴 시작 -->
<td width="200px" height="400px">
<%@ include file="menu.jsp"%>
</td>
<!-- 메뉴 끝 -->
<!-- 메인 시작 -->
<td width="800px" height="400px">
<%
if (menu == null) {
%>
<%@ include file="main.jsp"%>
<%
} else if ("info".equals(menu)) {
%>
<%@ include file="info.jsp"%>
<%
} else if ("login".equals(menu)) {
%>
<%@ include file="login.jsp"%>
<%
} else if ("board".equals(menu)) {
%>
<%@ include file="board.jsp"%>
<%
} else if ("google".equals(menu)) {
%>
<%@ include file="google.jsp"%>
<%
}
%>
</td>
<!-- 메인 끝 -->
</tr>
</table>
<!-- body 끝 -->
</td>
</tr>
<tr>
<td width="1000px" height="30px">
<!-- bottom 시작 -->
<%@ include file="bottom.jsp"%>
<!-- bottom 끝 -->
</td>
</tr>
</table>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" bordercolor="green">
<tr>
<td>top 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" bordercolor="blue">
<tr>
<td valign="top">
<table>
<tr>
<td><a href="./index.jsp?menu=info">소개</a></td>
</tr>
<tr>
<td><a href="./index.jsp?menu=login">로그인</a></td>
</tr>
<tr>
<td><a href="./index.jsp?menu=board">게시판</a></td>
</tr>
<tr>
<td><a href="./index.jsp?menu=google">구글</a></td>
</tr>
</table>
</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" bordercolor="yellow">
<tr>
<td>bottom 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" bordercolor="red">
<tr>
<td>body 메인 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" style="background-color:green">
<tr>
<td>info 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" style="background-color:blue">
<tr>
<td>login 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" style="background-color:red">
<tr>
<td>board 영역</td>
</tr>
</table>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" style="background-color:yellow">
<tr>
<td>google 영역</td>
</tr>
</table>
<뉴스 목록 - 전체 갱신>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%!
int x = 1; // 전역변수 선언하기
public String newsItem(String[] news) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < news.length; i++){
if(i == (x - 1)){
sb.append("<table width='500px' border='1px'>");
sb.append("<tr><td>"+news[i]+"</td></tr>");
sb.append("</table>");
} // end of if
} // end of for
String choice = sb.toString();
return choice;
} // end of newsItem
%>
<%
// 스크립틀릿
String news[] = {
"젤렌스키 “전투기 지원을”…영·프·독 정상은 ‘확답’ 안 해",
"유럽연합, 조만간 러시아 열번째 제재… 선전 매체 겨냥",
"[속보] 튀르키예·시리아 강진 누적 사망자 2만명 넘어",
"한국에서 왔니? 목에 걸어 中공항서 한국에 ‘보복조치’ 뒤끝",
"우크라전 350일…하르키우 등 러시아 포격 증가"};
String data = "";
switch(x){
case 1:
data = newsItem(news);
x++;
break;
case 2:
data = newsItem(news);
x++;
break;
case 3:
data = newsItem(news);
x++;
break;
case 4:
data = newsItem(news);
x++;
break;
case 5:
data = newsItem(news);
x = 1;
break;
} // end of switch
// 기존에 읽어온 기사정보 지우기
out.clear();
out.print(data);
%>
< 뉴스 목록 - jquery 자동 갱신 처리>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
// 호출하지 않아도 자동 실행
// jquery(document) -> $로 바꿈 $(document)
// window <- document <- ready(function(){실행문: 변수선언, if문, switch문, i+j}) 함수
$(document).ready(() => {
// 실행문이 오는 자리()
start = () => {
setInterval(autoReload, 2000); // 2초 대기(지연) 후 autoReload 호출
}
start();
}) // end of ready - DOM을 다 읽었으면
</script>
<div id="d_news">뉴스 준비중 ...</div>
<script>
const autoReload = () => {
console.log('autoReload 호출')
// ajax함수는 jquery.min.js가 제공하는 API이다
// ajax함수는 결국 XMLHttpRequest를 대신함
// const ajax = new XMLHttpRequest();
// ajax.open("GET", url, false)
// ajax.send()
/*
XMLHttpRequest (XHR) 객체는 서버와 상호작용할 때 사용함
페이지의 새로고침 없이도 데이터를 가져올 수 있다(페이지의 일부를 업데이트 가능)
*/
$.ajax({
type: "GET",
url: "newsList.jsp",
success: function (data) { // 성공했을 때
// console.log(data);
$("#d_news").html(data);
},
error: function(request, status, error){
console.log("error")
console.log("error" + request.status)
console.log("error" + request.responseText)
}
});
}
</script>
</body>
</html>
<!--
HTML(단방향, 변수선언이나 제어문 지원안됨, 이벤트 처리 불가)은 순차적으로 실행
자바스크립트 코드의 위치에 따라서 document.querySelector(id or class or element)
-> 항상 선언이 먼저여야함
자바스크립트 위치
1) head 안에 -> 전역변수, 함수 선언할 때(호출해야 실행됨)
만약 이것을 지연하고 싶다면 defer 사용(Html, DOM Tree를 그릴 때까지 -> 그래야 querySelector 사용가능)
2) body 안에 -> 호출하지 않아도 실행됨(단, 함수로 선언된 부분은 제외)
자바스크립트로 화면을 그릴 수 있다
document.write("<font color='red' size=18></font>")
write는 어디다 쓰는 것인가? -> 브라우저
Java는 브라우저에 쓸 수 없다!
아래의 system은 로컬pc일뿐 브라우저 아님
System.out.println("<b>굵은글씨</b>")
아래의 out은 브라우저에 써준다(servlet이 제공하는 API)
out.print();
마임타입에 따라서 jsp라고 쓰고 html | json이라고 읽을수있음
메인타입/서브타입
image/png | jpe | gif
text/html | css | javascript | module | babel
-->
확장자가 아니라 mime타입에 따라 브라우저 출력 결정된다
타입이 json이어도 그냥 memberList 찍으면 json형식 x
<%@ page language="java" contentType="application/json; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.util.*, com.google.gson.Gson" %>
<%
List<Map<String, Object>> memberList = new ArrayList<>();
Map<String, Object> map = new HashMap<>();
map.put("mem_id", "tomato");
memberList.add(map);
map = new HashMap<>();
map.put("mem_id", "apple");
memberList.add(map);
map = new HashMap<>();
map.put("mem_id", "banana");
memberList.add(map);
// 구글에서는 Gson.jar 라이브러리 지원
Gson g = new Gson();
// JSON포맷으로 출력하려면 아래와같이 해야함
String temp = g.toJson(memberList);
out.print(temp);
// 아래와 같이 자료구조를 출력하면 JSON포맷이 아니다 -> 자바스크립트에서 읽을수가 없다(조회결과없음)
out.print(memberList);
%>
<index 테이블 메인과 뉴스 목록 합치기>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
String menu = request.getParameter("menu"); // info or login or board or google
out.print(menu);
%>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<title>패턴1</title>
<style>
a {
text-decoration: none;
}
</style>
</head>
<body>
<script defer type="text/javascript">
$(document).ready(() => {
start = () => {
setInterval(autoReload, 2000); // 2초 대기(지연) 후 autoReload 호출
}
start();
}) // end of ready - DOM을 다 읽었으면
</script>
<script>
const autoReload = () => {
console.log('autoReload 호출')
$.ajax({
type: "GET",
url: "newsList.jsp",
success: function (data) { // 성공했을 때
console.log(data);
$("#d_news").html(data);
},
error: function(request, status, error){
console.log("error")
console.log("error" + request.status)
console.log("error" + request.responseText)
}
});
}
</script>
<!-- 태그는 중첩해서 사용 가능함 -->
<!-- 크기는 %로 주는 것이 좋다(가변적이니까) -->
<table border="1" align="center" width="1000px">
<tr>
<td width="100%" height="50px">
<!-- top 시작 -->
<%@ include file="top.jsp"%>
<!-- top 끝 -->
</td>
</tr>
<tr>
<td>
<!-- body 시작 -->
<table>
<tr>
<!-- 메뉴 시작 -->
<td width="200px" height="400px">
<%@ include file="menu.jsp"%>
</td>
<!-- 메뉴 끝 -->
<!-- 메인 시작 -->
<td width="800px" height="400px">
<%
if (menu == null) {
%>
<%@ include file="main.jsp"%>
<%
} else if ("info".equals(menu)) {
%>
<%@ include file="info.jsp"%>
<%
} else if ("login".equals(menu)) {
%>
<%@ include file="login.jsp"%>
<%
} else if ("board".equals(menu)) {
%>
<%@ include file="board.jsp"%>
<%
} else if ("google".equals(menu)) {
%>
<%@ include file="google.jsp"%>
<%
}
%>
</td>
<!-- 메인 끝 -->
</tr>
</table>
<!-- body 끝 -->
</td>
</tr>
<tr>
<td width="1000px" height="30px">
<!-- bottom 시작 -->
<%@ include file="bottom.jsp"%>
<!-- bottom 끝 -->
</td>
</tr>
</table>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<table border="1" width="100%" height="100%" bordercolor="red">
<tr>
<td>
<div id = "d_news">...</div>
</td>
</tr>
</table>
<bootstrap5 활용>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Insert title here</title>
<link rel="stylesheet" href="./step1.css" />
<%@ include file = "../../common/bootstrapCommon.jsp" %>
</head>
<body>
<!-- ==================== [[ Navbar start ]] ==================== -->
<nav class="navbar navbar-expand-lg bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
Dropdown
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider" /></li>
<li>
<a class="dropdown-item" href="#">Something else here</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">Disabled</a>
</li>
</ul>
<form class="d-flex" role="search">
<input
class="form-control me-2"
type="search"
placeholder="Search"
aria-label="Search"
/>
<button class="btn btn-outline-success" type="submit">
Search
</button>
</form>
</div>
</div>
</nav>
<!-- ==================== [[ Navbar end ]] ==================== -->
<!-- ==================== [[ Carousel start ]] ==================== -->
<div
id="carouselExampleCaptions"
class="carousel slide"
data-bs-ride="false"
>
<div class="carousel-indicators">
<button
type="button"
data-bs-target="#carouselExampleCaptions"
data-bs-slide-to="0"
class="active"
aria-current="true"
aria-label="Slide 1"
></button>
<button
type="button"
data-bs-target="#carouselExampleCaptions"
data-bs-slide-to="1"
aria-label="Slide 2"
></button>
<button
type="button"
data-bs-target="#carouselExampleCaptions"
data-bs-slide-to="2"
aria-label="Slide 3"
></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img
src="../../images/sample/beach-daylight.jpg"
class="d-block w-100"
alt="..."
/>
<div class="carousel-caption d-none d-md-block">
<h5>First slide label</h5>
<p>Some representative placeholder content for the first slide.</p>
</div>
</div>
<div class="carousel-item">
<img
src="../../images/sample/camera_comp.jpg"
class="d-block w-100"
alt="..."
/>
<div class="carousel-caption d-none d-md-block">
<h5>Second slide label</h5>
<p>Some representative placeholder content for the second slide.</p>
</div>
</div>
<div class="carousel-item">
<img
src="../../images/sample/camping-goods.jpg"
class="d-block w-100"
alt="..."
/>
<div class="carousel-caption d-none d-md-block">
<h5>Third slide label</h5>
<p>Some representative placeholder content for the third slide.</p>
</div>
</div>
</div>
<button
class="carousel-control-prev"
type="button"
data-bs-target="#carouselExampleCaptions"
data-bs-slide="prev"
>
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button
class="carousel-control-next"
type="button"
data-bs-target="#carouselExampleCaptions"
data-bs-slide="next"
>
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<!-- ==================== [[ Carousel end ]] ==================== -->
<!-- ==================== [[ Card end ]] ==================== -->
<div class="card-group">
<div class="card" style="width: 18rem;">
<img src="../../images/sample/도서-1.jpg" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
<div class="card" style="width: 18rem;">
<img src="../../images/sample/도서-2.jpg" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
<div class="card" style="width: 18rem;">
<img src="../../images/sample/도서-4.jpg" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="#" class="btn btn-primary">Go somewhere</a>
</div>
</div>
</div>
<!-- ==================== [[ Card end ]] ==================== -->
<!-- ==================== [[ footer start ]] ==================== -->
<nav class="navbar navbar-expand-sm bg-dark navbar-dark fixed-bottom">
<div class="container-fluid">
<span class="footer">자바캠프 Copyright © 2023</span>
</div>
</nav>
<!-- ==================== [[ footer end ]] ==================== -->
</body>
</html>
.card-group {
display: inline-block;
margin: 1.5rem;
margin-bottom: 4rem;
}
.card-group.card {
padding-right: 1rem;
}
.card-img-top {
height: 100%;
}
.footer {
display: flex;
justify-content: center;
color: white;
font-size: 1rem;
}
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day54 (1) | 2023.02.14 |
---|---|
국비 지원 개발자 과정_Day53 (1) | 2023.02.13 |
국비 지원 개발자 과정_Day51 (0) | 2023.02.09 |
국비 지원 개발자 과정_Day50 (0) | 2023.02.08 |
국비 지원 개발자 과정_Day49 (0) | 2023.02.07 |
댓글