홈페이지 구현하기(7)
- 게시판 만들기
- header.jsp 게시판 경로 추가
<a href="<%=root%>/board/list.jsp">게시판</a>
- SQL Board 테이블, 시퀀스 추가
( 게시글번호, 작성자, 제목, 내용, 작성일, 조회수, 댓글수, 상위게시글번호, 그룹, 차수 )
create table board(
board_no number primary key,
board_writer references member(member_id) on delete set null,
board_title varchar2(300) not null,
board_content varchar2(4000) not null,
board_time date default sysdate not null,
board_read number default 0 not null,
board_reply number default 0 not null,
board_superno number not null,
board_groupno number not null,
board_depth number not null
);
create sequence board_seq;
- BoardDto 만들기
package home.beans;
import java.sql.Date;
public class BoardDto {
private int boardNo;
private String boardWriter;
private String boardTitle;
private String boardContent;
private Date boardTime;
private int boardRead;
private int boardReply;
private int boardSuperno;
private int boardGroupno;
private int boardDepth;
public BoardDto() {
super();
}
public int getBoardNo() {
return boardNo;
}
public void setBoardNo(int boardNo) {
this.boardNo = boardNo;
}
public String getBoardWriter() {
return boardWriter;
}
public void setBoardWriter(String boardWriter) {
this.boardWriter = boardWriter;
}
public String getBoardTitle() {
return boardTitle;
}
public void setBoardTitle(String boardTitle) {
this.boardTitle = boardTitle;
}
public String getBoardContent() {
return boardContent;
}
public void setBoardContent(String boardContent) {
this.boardContent = boardContent;
}
public Date getBoardTime() {
return boardTime;
}
public void setBoardTime(Date boardTime) {
this.boardTime = boardTime;
}
public int getBoardRead() {
return boardRead;
}
public void setBoardRead(int boardRead) {
this.boardRead = boardRead;
}
public int getBoardReply() {
return boardReply;
}
public void setBoardReply(int boardReply) {
this.boardReply = boardReply;
}
public int getBoardSuperno() {
return boardSuperno;
}
public void setBoardSuperno(int boardSuperno) {
this.boardSuperno = boardSuperno;
}
public int getBoardGroupno() {
return boardGroupno;
}
public void setBoardGroupno(int boardGroupno) {
this.boardGroupno = boardGroupno;
}
public int getBoardDepth() {
return boardDepth;
}
public void setBoardDepth(int boardDepth) {
this.boardDepth = boardDepth;
}
}
- MemberFilter board 추가(회원만 접속)
@WebFilter(urlPatterns = "/board/*")
[ 목록 / 검색 ]
- BoardDao 목록 기능 만들기
package home.beans;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class BoardDao {
public static final String USERNAME = "계정아이디", PASSWORD = "계정비밀번호";
//목록기능
public List<BoardDto> list() throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "select * from board order by board_no desc";//최신순 - board_no가 pk라서 인덱스를 갖는다- 순서
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
List<BoardDto> list = new ArrayList<>();
while(rs.next()) {
BoardDto boardDto = new BoardDto();
boardDto.setBoardNo(rs.getInt("board_no"));
boardDto.setBoardWriter(rs.getString("board_writer"));
boardDto.setBoardTitle(rs.getString("board_title"));
boardDto.setBoardContent(rs.getString("board_content"));
boardDto.setBoardTime(rs.getDate("board_time"));
boardDto.setBoardRead(rs.getInt("board_read"));
boardDto.setBoardReply(rs.getInt("board_reply"));
boardDto.setBoardSuperno(rs.getInt("board_superno"));
boardDto.setBoardGroupno(rs.getInt("board_groupno"));
boardDto.setBoardDepth(rs.getInt("board_depth"));
list.add(boardDto);
}
con.close();
return list;
}
}
- BoardDao 검색 기능 만들기
//검색기능
public List<BoardDto> search(String column, String keyword) throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "select * from board where instr(#1, ?) > 0 order by board_no desc";
sql = sql.replace("#1", column);//정적 바인딩
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1, keyword);
ResultSet rs = ps.executeQuery();
List<BoardDto> list = new ArrayList<>();
while(rs.next()) {
BoardDto boardDto = new BoardDto();
boardDto.setBoardNo(rs.getInt("board_no"));
boardDto.setBoardWriter(rs.getString("board_writer"));
boardDto.setBoardTitle(rs.getString("board_title"));
boardDto.setBoardContent(rs.getString("board_content"));
boardDto.setBoardTime(rs.getDate("board_time"));
boardDto.setBoardRead(rs.getInt("board_read"));
boardDto.setBoardReply(rs.getInt("board_reply"));
boardDto.setBoardSuperno(rs.getInt("board_superno"));
boardDto.setBoardGroupno(rs.getInt("board_groupno"));
boardDto.setBoardDepth(rs.getInt("board_depth"));
list.add(boardDto);
}
con.close();
return list;
}
- 게시판 목록 / 검색 (list.jsp) 만들기
<%@page import="home.beans.BoardDto"%>
<%@page import="java.util.List"%>
<%@page import="home.beans.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 입력 : 검색분류(column), 검색어(keyword) --%>
<%
String column = request.getParameter("column");
String keyword = request.getParameter("keyword");
%>
<%-- 처리 --%>
<%
boolean search = column != null && !column.isEmpty() && keyword != null && !keyword.isEmpty();
BoardDao boardDao = new BoardDao();
List<BoardDto> list;
if(search){
list = boardDao.search(column, keyword);
}
else{
list = boardDao.list();
}
%>
<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>
<h2>회원 게시판</h2>
<h6>타인에 대한 무분별한 비판은 제재 대상입니다</h6>
<a href="write.jsp">글쓰기</a>
<br><br>
<table border="1" width="90%">
<thead>
<tr>
<th>번호</th>
<th width="45%">제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
</thead>
<tbody align="center">
<%for(BoardDto boardDto : list){ %>
<tr>
<td><%=boardDto.getBoardNo()%></td>
<td align="left"><a href="detail.jsp?boardNo=<%=boardDto.getBoardNo()%>"><%=boardDto.getBoardTitle()%></a></td>
<td><%=boardDto.getBoardWriter()%></td>
<td><%=boardDto.getBoardTime()%></td>
<td><%=boardDto.getBoardRead()%></td>
</tr>
<%} %>
</tbody>
</table>
<br>
<a href="write.jsp">글쓰기</a>
<!-- 페이지 네비게이터 -->
<br><br>
[이전] 1 2 3 4 5 6 7 8 9 10 [다음]
<br><br>
<!-- 검색창 -->
<form action="list.jsp" method="get">
<select name="column">
<%if(column != null && column.equals("board_title")){ %>
<option value="board_title" selected>제목</option>
<%}else{ %>
<option value="board_title">제목</option>
<%} %>
<%if(column != null && column.equals("board_content")){ %>
<option value="board_content" selected>내용</option>
<%}else{ %>
<option value="board_content">내용</option>
<%} %>
<%if(column != null && column.equals("board_writer")){ %>
<option value="board_writer" selected>작성자</option>
<%}else{ %>
<option value="board_writer">작성자</option>
<%} %>
</select>
<%if(keyword == null){ %>
<input type="search" name="keyword" placeholder="검색어 입력" required>
<%}else{ %>
<input type="search" name="keyword" placeholder="검색어 입력" required value="<%=keyword%>">
<%} %>
<input type="submit" value="검색">
</form>
<jsp:include page="/template/footer.jsp"></jsp:include>
[ 상세보기 / 조회수 ]
- BoardDao 상세보기 기능 만들기
//상세보기 기능
public BoardDto get(int boardNo) throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "select * from board where board_no = ?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, boardNo);
ResultSet rs = ps.executeQuery();
BoardDto boardDto;
if(rs.next()) {
boardDto = new BoardDto();
boardDto.setBoardNo(rs.getInt("board_no"));
boardDto.setBoardWriter(rs.getString("board_writer"));
boardDto.setBoardTitle(rs.getString("board_title"));
boardDto.setBoardContent(rs.getString("board_content"));
boardDto.setBoardTime(rs.getDate("board_time"));
boardDto.setBoardRead(rs.getInt("board_read"));
boardDto.setBoardReply(rs.getInt("board_reply"));
boardDto.setBoardSuperno(rs.getInt("board_superno"));
boardDto.setBoardGroupno(rs.getInt("board_groupno"));
boardDto.setBoardDepth(rs.getInt("board_depth"));
}
else {
boardDto = null;
}
con.close();
return boardDto;
}
- BoardDao 상세보기 누르면 조회수 증가 기능 만들기 (남의 글일 경우에만 증가)
//남의 글일 경우에만 조회수를 증가하는 기능
public boolean readUp(int boardNo, String memberId) throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "update board "
+ "set board_read = board_read + 1 "
+ "where board_no = ? and board_writer != ?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, boardNo);
ps.setString(2, memberId);
int result = ps.executeUpdate();
con.close();
return result > 0;
}
- 게시판 상세보기 (detail.jsp) 만들기
<%@page import="java.util.HashSet"%>
<%@page import="java.util.Set"%>
<%@page import="home.beans.BoardDto"%>
<%@page import="home.beans.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 입력 : 게시글번호(boardNo) --%>
<%
int boardNo = Integer.parseInt(request.getParameter("boardNo"));
%>
<%-- 처리 --%>
<%
String memberId = (String)session.getAttribute("ses");
BoardDao boardDao = new BoardDao();
/**
조회수 중복 방지에 대한 시나리오
1. 본인 글에 대한 조회 수 증가를 방지한다.
2. 한 번 읽은 글에 대한 추가 조회 수 증가를 방지한다.
= 세션에 사용자가 읽은 글 번호를 추가하여 관리하도록 구현
3. IP를 이용한 조회 수 증가를 방지한다.
= 접속자 IP 확인 명령을 통한 IP 비교
= 사용자에게 반드시 이용 고지를 해야함(IP는 개인정보)
= 전체 사용자에게 영항을 줄 수 있는 저장소가 필요
*/
//1. boardViewedNo 라는 이름의 저장소를 세션에서 꺼내어 본다.
Set<Integer> boardViewedNo = (Set<Integer>)session.getAttribute("boardViewedNo");
//2. boardViewedNo 가 null 이면 "처음 글을 읽는 상태"임을 말하므로 저장소를 신규로 생성
if(boardViewedNo == null){
boardViewedNo = new HashSet<>();
//System.out.println("처음으로 글을 읽기 시작했습니다(저장소 생성)");
}
//3. 현재 글 번호를 저장소에 추가해본다
//3-1. 추가가 된다면 이 글은 처음 읽는 글
//3-2. 추가가 안된다면 이 글은 두 번 이상 읽은 글
if (boardViewedNo.add(boardNo)) {//처음 읽은 글인 경우
boardDao.readUp(boardNo, memberId);//조회수 증가(남에 글일때만)
//System.out.println("이 글은 처음 읽는 글입니다");
} else {
//System.out.println("이 글은 읽은 적이 있습니다");
}
//System.out.println("저장소 : " + boardViewedNo);
//4. 저장소 갱신
session.setAttribute("boardViewedNo", boardViewedNo);
BoardDto boardDto = boardDao.get(boardNo);//단일조회
//본인 글인지 아닌지를 판정하는 변수
//boolean owner = 세션의ses 값과 게시글 작성자가 같은가?;
boolean owner = boardDto.getBoardWriter().equals(memberId);
%>
<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>
<h2><%=boardDto.getBoardNo()%>번 게시글</h2>
<table border="1" width="80%">
<tbody>
<tr>
<td>
<h3><%=boardDto.getBoardTitle()%></h3>
</td>
</tr>
<tr>
<td>
등록일 : <%=boardDto.getBoardTime()%>
|
작성자 : <%=boardDto.getBoardWriter()%>
|
조회수 : <%=boardDto.getBoardRead()%>
</td>
</tr>
<!-- 답답해 보이지 않도록 기본높이를 부여 -->
<!--
pre 태그를 사용하여 내용을 있는 그대로 표시되도록 설정
(주의) 태그 사이에 쓸데없는 엔터, 띄어쓰기 등이 들어가지 않도록 해야 한다.(모두 표시된다)
-->
<tr height="250" valign="top">
<td>
<pre><%=boardDto.getBoardContent()%></pre>
</td>
</tr>
<tr>
<td align="right">
<a href="write.jsp">글쓰기</a>
<a href="list.jsp">목록보기</a>
<%if(owner){ %>
<a href="edit.jsp?boardNo=<%=boardDto.getBoardNo()%>">수정하기</a>
<%-- <a href="delete.txt?boardNo=<%=boardNo%>">삭제하기</a> --%>
<a href="delete.txt?boardNo=<%=boardDto.getBoardNo()%>">삭제하기</a>
<%} %>
</td>
</tr>
</tbody>
</table>
<jsp:include page="/template/footer.jsp"></jsp:include>
[ 등록 ]
- BoardDao 등록 기능 만들기 (1)
등록 후 상세보기로 바로 갈 수 있게 번호(시퀀스)를 먼저 생성해서 등록한다
//번호 생성 기능 : 번호를 미리 생성해두어야 할 필요가 있는 경우 사용
public int getSequence() throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "select board_seq.nextval from dual";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs= ps.executeQuery();
rs.next();
//int seq = rs.getInt("nextval");
int seq = rs.getInt(1);
con.close();
return seq;
}
- BoardDao 등록 기능 만들기 (2)
//등록 기능(2) : 번호를 미리 생성해놓은 경우의 추가 기능
public void write2(BoardDto boardDto) throws Exception{
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "insert into board values(?, ?, ?, ?, sysdate, 0, 0, 0, 0, 0)";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, boardDto.getBoardNo());
ps.setString(2, boardDto.getBoardWriter());
ps.setString(3, boardDto.getBoardTitle());
ps.setString(4, boardDto.getBoardContent());
ps.execute();
con.close();
}
- 게시글 등록 처리 (BoardWriteServlet.java) 만들기
package home.servlet.board;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import home.beans.BoardDao;
import home.beans.BoardDto;
@WebServlet(urlPatterns = "/board/write.txt")
public class BoardWriteServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//입력 : BoardDto(boardTitle, boardContent)
//req.setCharacterEncoding("UTF-8");//EncodingFilter에서 처리
BoardDto boardDto = new BoardDto();
boardDto.setBoardTitle(req.getParameter("boardTitle"));
boardDto.setBoardContent(req.getParameter("boardContent"));
//아이디는 세션에서 수집하여 추가
boardDto.setBoardWriter((String)req.getSession().getAttribute("ses"));
//(2) 번호를 알아내면서 등록
BoardDao boardDao = new BoardDao();
int boardNo = boardDao.getSequence();//시퀀스 번호 생성
boardDto.setBoardNo(boardNo);//게시글 데이터에 생성된 번호 추가
boardDao.write2(boardDto);//게시글 등록
//(2) detail.jsp로 리다이렉트
resp.sendRedirect("detail.jsp?boardNo="+boardNo);
}
catch(Exception e) {
e.printStackTrace();
resp.sendError(500);
}
}
}
- 게시글 등록 입력 (write.jsp) 만들기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 입력 --%>
<%-- 처리 --%>
<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>
<h2>게시글 작성</h2>
<form action="write.txt" method="post">
<table border="0">
<tbody>
<tr>
<th>제목</th>
<td><input type="text" name="boardTitle" required></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="boardContent" required rows="10" cols="60"></textarea>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2" align="right">
<input type="submit" value="등록">
</td>
</tr>
</tfoot>
</table>
</form>
<jsp:include page="/template/footer.jsp"></jsp:include>
[ 삭제 ]
- BoardDao 삭제 기능 만들기
//삭제 기능
public boolean delete(int boardNo) throws Exception{
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "delete board where board_no = ?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, boardNo);
int result = ps.executeUpdate();
con.close();
return result > 0;
}
- 게시글 삭제 처리 (BoardDeleteServlet.java) 만들기
package home.servlet.board;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import home.beans.BoardDao;
@WebServlet(urlPatterns = "/board/delete.txt")
public class BoardDeleteServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//입력
int boardNo = Integer.parseInt(req.getParameter("boardNo"));
//처리
BoardDao boardDao = new BoardDao();
boolean success = boardDao.delete(boardNo);
//출력
if(success) {
resp.sendRedirect("list.jsp");
}
else {
resp.sendError(404);
}
}
catch(Exception e) {
e.printStackTrace();
resp.sendError(500);
}
}
}
[ 수정 ]
- BoardDao 수정 기능 만들기
//수정 기능
public boolean edit(BoardDto boardDto) throws Exception {
Connection con = JdbcUtils.connect(USERNAME, PASSWORD);
String sql = "update board set board_title=?, board_content=? where board_no=?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setString(1, boardDto.getBoardTitle());
ps.setString(2, boardDto.getBoardContent());
ps.setInt(3, boardDto.getBoardNo());
int result = ps.executeUpdate();
con.close();
return result > 0;
}
- 게시글 수정 처리 (BoardEditServlet.java) 만들기
package home.servlet.board;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import home.beans.BoardDao;
import home.beans.BoardDto;
@WebServlet(urlPatterns = "/board/edit.txt")
public class BoardEditServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
//입력 : BoardDto(boardNo + boardTitle + boardContent)
BoardDto boardDto = new BoardDto();
boardDto.setBoardNo(Integer.parseInt(req.getParameter("boardNo")));
boardDto.setBoardTitle(req.getParameter("boardTitle"));
boardDto.setBoardContent(req.getParameter("boardContent"));
//처리
BoardDao boardDao = new BoardDao();
boolean success = boardDao.edit(boardDto);
//출력 : detail.jsp
if(success) {
resp.sendRedirect("detail.jsp?boardNo="+boardDto.getBoardNo());
}
else {
resp.sendError(404);
}
}
catch(Exception e) {
e.printStackTrace();
resp.sendError(500);
}
}
}
- 게시글 수정 입력 (edit.jsp) 만들기
<%@page import="home.beans.BoardDto"%>
<%@page import="home.beans.BoardDao"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- 입력 --%>
<%
int boardNo = Integer.parseInt(request.getParameter("boardNo"));
%>
<%-- 처리 --%>
<%
BoardDao boardDao = new BoardDao();
BoardDto boardDto = boardDao.get(boardNo);
%>
<%-- 출력 --%>
<jsp:include page="/template/header.jsp"></jsp:include>
<h2>게시글 수정</h2>
<form action="edit.txt" method="post">
<input type="hidden" name="boardNo" value="<%=boardDto.getBoardNo()%>">
<table border="0">
<tbody>
<tr>
<th>제목</th>
<td><input type="text" name="boardTitle" required value="<%=boardDto.getBoardTitle()%>"></td>
</tr>
<tr>
<th>내용</th>
<td>
<textarea name="boardContent" required
rows="10" cols="60"><%=boardDto.getBoardContent()%></textarea>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="2" align="right">
<input type="submit" value="수정">
</td>
</tr>
</tfoot>
</table>
</form>
<jsp:include page="/template/footer.jsp"></jsp:include>
[ 수정/삭제 본인확인 필터 ]
- 게시판 본인확인 필터 (BoardSelfFilter.java) 만들기
게시판 수정, 삭제 기능에 대한 본인확인 필터
시나리오
1. 모든 요청에는 boardNo라는 파라미터가 존재한다(만약에 없으면 차단)
2. boardNo를 이용해서 해당 번호 게시글의 "작성자(boardWriter)"를 조회한다.
3. 세션을 조회하여 현재 로그인한 사용자의 ID를 알아낸다.
4. 2번과 3번에서 알아낸 정보를 비교한다.
5-1. 4번의 결과가 같다고 나올 경우 본인 글이므로 통과시킨다.
5-2. 4번의 결과가 다르다고 나올 경우 본인 글이 아니므로 차단시킨다.
package home.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import home.beans.BoardDao;
import home.beans.BoardDto;
@WebFilter(urlPatterns = {
"/board/edit.jsp", "/board/edit.txt",
"/board/delete.txt"
})
public class BoardSelfFilter implements Filter{
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)request;
HttpServletResponse resp = (HttpServletResponse)response;
try {
req.setCharacterEncoding("UTF-8");
int boardNo = Integer.parseInt(req.getParameter("boardNo"));
BoardDao boardDao = new BoardDao();
BoardDto boardDto = boardDao.get(boardNo);
String memberId = (String)req.getSession().getAttribute("ses");
if(boardDto == null) {//대상 게시글이 없으면(잘못된 번호)
resp.sendError(404);//404 not found
}
else if(memberId == null) {//비회원이면
resp.sendError(401);//401 unauthorized
}
else if(boardDto.getBoardWriter().equals(memberId)) {//아이디 일치(본인)
chain.doFilter(request, response);//통과
}
else {
resp.sendError(403);//403 forbidden
}
}
catch(Exception e) {
e.printStackTrace();
resp.sendError(500);
}
}
}
'Java 웹 개발' 카테고리의 다른 글
21.10.28 - 웹 개발 입문 54일차 (0) | 2021.10.28 |
---|---|
21.10.27 - 웹 개발 입문 53일차 (0) | 2021.10.27 |
21.10.25 - 웹 개발 입문 51일차 (0) | 2021.10.25 |
21.10.22 - 웹 개발 입문 50일차 (0) | 2021.10.23 |
21.10.21 - 웹 개발 입문 49일차 (0) | 2021.10.21 |