- Search 기능 만들어보기 + 결과 페이징


어제 만들어본 페이징 기능을 활용하여 검색 기능을 추가했습니다
페이지당 보여지는 최대게시글 갯수 , 하단페이징의 갯수까지 동일하게 맞췄습니다
검색 결과가 페이지당 최대 글갯수인 9보다 작아진다면 [이전] , [다음] 기능을 비활성화 합니다
- Search + Paging - Controller
서치의 페이징을 쉽게하기 위하여 전날에 사용했던 Controller의 Paging 메소드를 수정하여 사용했습니다
// pagin Mapping과 search Mapping을 합친것
@GetMapping("/paging")
public String paging(@RequestParam(value = "page", required = false, defaultValue = "1") int page,
페이징을 위한 파라미터를 받아주는 @RequestParam
@RequestParam(value = "q", required = false, defaultValue = "") String q,
페이징된 리스트에서 만들어졌기에 사용자가 검색한 값을 받아주는 @RequestParam 추가
@RequestParam(value = "type", required = false, defaultValue = "boardTitle") String type,
페이징된 리스트에서 사용자가 설정한 검색 옵션 값을 받아주는 @RequestParam 추가
Model model) {
3가지 파라미터 모두 required = false로 입력값이 이상할 경우
설정한 defaultValue값으로 기본설정된 값이 전달된다
List<BoardDTO> boardDTOList = null;
사용자가 요청한 페이지에 해당하는 글 목록 데이터
PageDTO pageDTO = null;
하단에 보여줄 페이지 번호 목록 데이터
if문에서 새로 덮어지며 지워지지만 혹시 모를 상황을 대비해
if문 전에 미리 null값으로 초기화를 한 모습
if (q.equals("")) {
검색어가 없다면 기본 페이징처리
boardDTOList = boardService.pagingList(page);
pageDTO = boardService.pagingParam(page);
전날과 마찬가지로 그냥 페이징 처리를 위한 메소드
} else {
있다면 검색어 리스트를 찾고 페이징 처리
boardDTOList = boardService.searchList(page, type, q);
pageDTO = boardService.pagingSearchParam(page, type, q);
페이지값 , 검색타입 , 검색어를 모두 매개변수로 받아
결과를 List , DTO에 리턴받아 담아줍니다
}
model.addAttribute("boardList", boardDTOList);
model.addAttribute("paging", pageDTO);
model.addAttribute("q", q);
model.addAttribute("type", type);
모두 model에 담아 페이징jsp에서 할당된 공간에 뿌려줍니다
return "boardPages/boardPaging";
}
- boardPaging.jsp
<tr>
<th colspan="5" style="padding: 5px">
<form action="/board/paging" method="get">
<select name="type">
<option value="boardTitle">제목</option>
<option value="boardWriter">작성자</option>
<option value="boardContents">내용</option>
</select>
<input type="text" name="q" placeholder="검색어를 입력하세요">
<input type="submit" value="검색">
</form>
</th>
</tr>
전날 만들었던 Paging.jsp 에서 따로 추가된 부분은 검색타입과 검색 결과를 받아주기 위한 공간 뿐입니다
그외에는 검색한 결과가 1페이지 이상일 경우를 대비해 type과 ,q 를 함께 리턴해주어야합니다

이렇게 검색으로 인한 글갯수가 9개가 넘어게가되면 다음페이지로 넘어가게 설정했기 떄문에
다음페이지로 넘어갈때 또한 검색 타입 , 검색 결과 , 넘어갈 페이지 번호를 파라미터로 넘겨주고
새로 DB에서 다음페이지에 해당하는 리스트의 정보를 담아와야 한다고 생각하면 편합니다

결국 비로그인용 게시판 웹을 만들어보는 시간이기 때문에
리스트에서 게시글을 누르면 상세페이지로 넘어가야합니다
상세페이지로 넘어가게만 만들거라면 굳이 검색타입 , 검색어 , 페이지번호는 보낼 필요가 없지만
상세페이지에서 뒤로가기를 눌렀을 경우 이전 화면을 출력하기 위하여 사용자의 편의를 위해 정보 저장을 위한
이전 페이징의 값을 파라미터로 넣어 함께 디테일로 이동해줍니다
- Detail - Controller
@GetMapping
public String boardDetail(@RequestParam("id") Long id, Model model,
이전 Detail과 다를게 없이 정보를 가져옵니다
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
@RequestParam(value = "q", required = false, defaultValue = "") String q,
@RequestParam(value = "type", required = false, defaultValue = "boardTitle") String type) {
이전에 있던 페이징의 정보를 @RequestParam을 사용하여 각변수에 담아줍니다
boardService.updateHits(id);
BoardDTO boardDTO = boardService.boardDetail(id);
if (boardDTO.getFileAttached() == 1) {
List<BoardFileDTO> boardFileList = boardService.findFile(id);
model.addAttribute("boardFileList", boardFileList);
}
List<CommentDTO> cList = commentService.boardComment(id);
if (cList.size() == 0) {
model.addAttribute("cList", null);
} else {
model.addAttribute("cList", cList);
}
model.addAttribute("boardDTO", boardDTO);
model.addAttribute("page",page);
model.addAttribute("q",q);
model.addAttribute("type",type);
model로 남아 함께 넘어갑니다
session을 사용하기에는 너무 무거워지기 때문에
조금 불편하더라도 이렇게 model로 담아 이동을하게 됩니다
return "boardPages/boardDetail";
}
이전 페이징리스트의 정보를 전달만할 뿐 따로 해주는건 없습니다
그렇기 떄문에 추가한 부분만 설명하고 넘어갑니다
- Detail.jsp

<body>
<tr>
<th colspan="6">
<input type="button" value="수정" onclick="goUpdate(${boardDTO.id})">
<input type="button" value="삭제" onclick="goDelete(${boardDTO.id})">
<input type="button" value="목록" onclick="goList()">
</th>
</tr>
</body>
<script>
const goList = () => {
const type = '${type}';
const q = '${q}';
const page = '${page}';
location.href = "/board/paging?page="+page +"&type="+type + "&q=" +q;
}
</script>
전체를 가져오기에는 너무 길기 떄문에 짧게 추가부분입니다
detail부분에서 > 목록 < 버튼을 누르게 되면 goList 함수가 실행되고
goLIst함수에서는 Controller로 잘 넘겨 받아온 model에 담긴 전 페이징 정보를 변수로 담아
location.href를 사용하여 파라미터 형식으로 Controller로 넘겨줍니다
- PagingSearch - Service (페이징 처리)
전날에 만들었던 pagingParam은 매개변수로 int page만 받지만
이번에 Search가 포함되면서 매개변수의 갯수가 늘어나 비슷하지만 다른
새메소드로 추가하게 되었습니다
리펙토링이 가능한 부분이 보이지만 그건 따로해보겠습니다
public PageDTO pagingSearchParam(int page,String type,String q) {
int pageLimit = 9;
한페이지에 보여줄 글 갯수
int blockLimit = 3;
하단에 보여줄 페이지 번호 갯수
Map<String, Object> pagingParams = new HashMap<>();
pagingParams.put("q", q);
pagingParams.put("type",type);
q = 검색값
type = 검색 타입 (제목 , 내용 , 작성자 중택1)
의 정보를 매개변수로 Controller에서 받아왔습니다
Mapper에는 2가지 변수를 넣어줄 수 없기 떄문에 압축을위해
Map에 각각의 키와 벨류로 값을 넣어 하나로 합쳐줍니다
int boardCount = boardRepository.boardSearchCount(pagingParams);
전체 글 갯수 조회
이부분은 전날의 페이징 count 메서드와 조금 기능이 다름 Mapper확인 필요
int maxPage = (int) (Math.ceil((double)boardCount / pageLimit));
전체 페이지 갯수 계산
int startPage = (((int)(Math.ceil((double) page / blockLimit))) - 1) * blockLimit + 1;
시작 페이지 값 계산(1, 4, 7, 10 ~~)
int endPage = startPage + blockLimit - 1;
마지막 페이지 값 계산(3, 6, 9, 12 ~~)
if (endPage > maxPage) {
endPage = maxPage;
}
전체 페이지 갯수가 계산한 endPage 보다 작을 때는 endPage 값을 maxPage 값과 같게 세팅
PageDTO pageDTO = new PageDTO();
pageDTO.setPage(page);
pageDTO.setMaxPage(maxPage);
pageDTO.setEndPage(endPage);
pageDTO.setStartPage(startPage);
이렇게 매개변수로 받은 정보를 토대로한
paging 데이터를 DTO에 담아 리턴합니다
return pageDTO;
}
- boardSearch - Repository & Mapper (전체글 갯수확인)
public int boardSearchCount(Map<String, Object> pagingParams) {
return sql.selectOne("Board.searchCount", pagingParams);
위에서 먼저 페이징처리 해야할 게시글의 갯수를 알아오기 위해
보냈던 pagingParams를 처리하는 Repository입니다
}
<select id="searchCount" parameterType="java.util.HashMap" resultType="Integer">
select count(id) from board_table where ${type} like concat('%', #{q}, '%')
게시글의 갯수를 알아보는데 Map의 정보가 필요한 이유입니다
검색에 맞는 글의 갯수만 알아야하기 때문인데요
Map에 담은 type 여기서는 (boardTitle , boardContents , boardWriter)중 하나이기 때문에
where의 비교 조건으로 사용되고
Map 담긴 다른 q는 검색어를 담은 값이기 때문에 MySQL쿼리문 양식에 맞춰 작성합니다
하지만 저희는 Mapper를 통하여 쿼리문을 DB로 넘기기 때문에
'%#{q}%' 로 작성하면 잘 전달되지 않는 오류가 있으므로
concat을 사용하여 각각 따로 이어 붙여 전달해줍니다
</select>
- PagingSearch - Service (리스트 뽑기)
public List<BoardDTO> searchList( int page,String type,String q) {
int pageLimit = 9;
한페이지에 보여줄 글의 갯수
int pagingStart = (page-1) * pageLimit;
한페이지에 보여줄 글 시작점
1페이지라면 0 부터시작 , 2페이지라면 9부터 시작 이렇게 끊어준다
Map<String, Object> pagingParams = new HashMap<>();
pagingParams.put("start", pagingStart);
pagingParams.put("limit", pageLimit);
계산이 끝난 페이지글 제한을 위한 변수를 담아줍니다
pagingParams.put("q", q);
pagingParams.put("type",type);
검색 조건에 맞는 정보를 검색해야하기에 함께 Map으로 담아가져갑니다
여기서도 위메소드의 페이징 처리와 동일하게 MySQL로 전달해야하기 때문에
데이터 압축을 위해 Map에 담아주는 작업을 진행합니다
하지만 시작과 끝을 정하여 보여줄 글 갯수를 지정하는 방식이기 때문에
약간의 수학계산이 필요합니다
List<BoardDTO> boardDTOList = boardRepository.searchList(pagingParams);
Repository를 통해 Mapper로 이동합니다
return boardDTOList;
}
- boardSearch - Repository , Mapper (마무리)
public List<BoardDTO> searchList(Map<String, Object> pagingParams) {
return sql.selectList("Board.search", pagingParams);
그냥 Map을 전달하기 위한 Repository 앞에서 고생을 너무했다
}
<select id="search" parameterType="java.util.HashMap" resultType="board">
select * from board_table where ${type} like concat('%', #{q} , '%')
type을 조건으로 가져온 q 검색값이 포함되는 List를 출력합니다
Mapper로 전달하는 like 쿼리문의 사용법이 잘 작동하지 않기 때문에
concat으로 따로 이어 붙여 전달해주었습니다
order by id asc limit #{start}, #{limit}
오름차순 정렬을하여 List에 담고 limt을 이용하여
시작부분과 최대갯수를 정해 출력받습니다
</select>
'나의 수업일지' 카테고리의 다른 글
인천 일보 아카데미 55일차 -회원제 게시판 만들기 - 회원가입 - 사진 프리뷰 / 회원가입 - 비밀번호 정규식 제약 조건 / 로그인 경고문 (0) | 2023.05.15 |
---|---|
인천 일보 아카데미 54일차 -로그인 게시판- 틀만들기 (저장용 내용X) / 업로드 파일 이름 출력 (0) | 2023.05.10 |
인천 일보 아카데미 53일차 -비로그인 게시판- List 페이징 기능 (0) | 2023.05.09 |
인천 일보 아카데미 53일차 - 비로그인 게시판 - 댓글 기능 (0) | 2023.05.08 |
인천 일보 아카데미 52일차 -1- 이미지 업로드 / detail에 이미지 보여주기 (0) | 2023.05.07 |
- Search 기능 만들어보기 + 결과 페이징


어제 만들어본 페이징 기능을 활용하여 검색 기능을 추가했습니다
페이지당 보여지는 최대게시글 갯수 , 하단페이징의 갯수까지 동일하게 맞췄습니다
검색 결과가 페이지당 최대 글갯수인 9보다 작아진다면 [이전] , [다음] 기능을 비활성화 합니다
- Search + Paging - Controller
서치의 페이징을 쉽게하기 위하여 전날에 사용했던 Controller의 Paging 메소드를 수정하여 사용했습니다
// pagin Mapping과 search Mapping을 합친것
@GetMapping("/paging")
public String paging(@RequestParam(value = "page", required = false, defaultValue = "1") int page,
페이징을 위한 파라미터를 받아주는 @RequestParam
@RequestParam(value = "q", required = false, defaultValue = "") String q,
페이징된 리스트에서 만들어졌기에 사용자가 검색한 값을 받아주는 @RequestParam 추가
@RequestParam(value = "type", required = false, defaultValue = "boardTitle") String type,
페이징된 리스트에서 사용자가 설정한 검색 옵션 값을 받아주는 @RequestParam 추가
Model model) {
3가지 파라미터 모두 required = false로 입력값이 이상할 경우
설정한 defaultValue값으로 기본설정된 값이 전달된다
List<BoardDTO> boardDTOList = null;
사용자가 요청한 페이지에 해당하는 글 목록 데이터
PageDTO pageDTO = null;
하단에 보여줄 페이지 번호 목록 데이터
if문에서 새로 덮어지며 지워지지만 혹시 모를 상황을 대비해
if문 전에 미리 null값으로 초기화를 한 모습
if (q.equals("")) {
검색어가 없다면 기본 페이징처리
boardDTOList = boardService.pagingList(page);
pageDTO = boardService.pagingParam(page);
전날과 마찬가지로 그냥 페이징 처리를 위한 메소드
} else {
있다면 검색어 리스트를 찾고 페이징 처리
boardDTOList = boardService.searchList(page, type, q);
pageDTO = boardService.pagingSearchParam(page, type, q);
페이지값 , 검색타입 , 검색어를 모두 매개변수로 받아
결과를 List , DTO에 리턴받아 담아줍니다
}
model.addAttribute("boardList", boardDTOList);
model.addAttribute("paging", pageDTO);
model.addAttribute("q", q);
model.addAttribute("type", type);
모두 model에 담아 페이징jsp에서 할당된 공간에 뿌려줍니다
return "boardPages/boardPaging";
}
- boardPaging.jsp
<tr>
<th colspan="5" style="padding: 5px">
<form action="/board/paging" method="get">
<select name="type">
<option value="boardTitle">제목</option>
<option value="boardWriter">작성자</option>
<option value="boardContents">내용</option>
</select>
<input type="text" name="q" placeholder="검색어를 입력하세요">
<input type="submit" value="검색">
</form>
</th>
</tr>
전날 만들었던 Paging.jsp 에서 따로 추가된 부분은 검색타입과 검색 결과를 받아주기 위한 공간 뿐입니다
그외에는 검색한 결과가 1페이지 이상일 경우를 대비해 type과 ,q 를 함께 리턴해주어야합니다

이렇게 검색으로 인한 글갯수가 9개가 넘어게가되면 다음페이지로 넘어가게 설정했기 떄문에
다음페이지로 넘어갈때 또한 검색 타입 , 검색 결과 , 넘어갈 페이지 번호를 파라미터로 넘겨주고
새로 DB에서 다음페이지에 해당하는 리스트의 정보를 담아와야 한다고 생각하면 편합니다

결국 비로그인용 게시판 웹을 만들어보는 시간이기 때문에
리스트에서 게시글을 누르면 상세페이지로 넘어가야합니다
상세페이지로 넘어가게만 만들거라면 굳이 검색타입 , 검색어 , 페이지번호는 보낼 필요가 없지만
상세페이지에서 뒤로가기를 눌렀을 경우 이전 화면을 출력하기 위하여 사용자의 편의를 위해 정보 저장을 위한
이전 페이징의 값을 파라미터로 넣어 함께 디테일로 이동해줍니다
- Detail - Controller
@GetMapping
public String boardDetail(@RequestParam("id") Long id, Model model,
이전 Detail과 다를게 없이 정보를 가져옵니다
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
@RequestParam(value = "q", required = false, defaultValue = "") String q,
@RequestParam(value = "type", required = false, defaultValue = "boardTitle") String type) {
이전에 있던 페이징의 정보를 @RequestParam을 사용하여 각변수에 담아줍니다
boardService.updateHits(id);
BoardDTO boardDTO = boardService.boardDetail(id);
if (boardDTO.getFileAttached() == 1) {
List<BoardFileDTO> boardFileList = boardService.findFile(id);
model.addAttribute("boardFileList", boardFileList);
}
List<CommentDTO> cList = commentService.boardComment(id);
if (cList.size() == 0) {
model.addAttribute("cList", null);
} else {
model.addAttribute("cList", cList);
}
model.addAttribute("boardDTO", boardDTO);
model.addAttribute("page",page);
model.addAttribute("q",q);
model.addAttribute("type",type);
model로 남아 함께 넘어갑니다
session을 사용하기에는 너무 무거워지기 때문에
조금 불편하더라도 이렇게 model로 담아 이동을하게 됩니다
return "boardPages/boardDetail";
}
이전 페이징리스트의 정보를 전달만할 뿐 따로 해주는건 없습니다
그렇기 떄문에 추가한 부분만 설명하고 넘어갑니다
- Detail.jsp

<body>
<tr>
<th colspan="6">
<input type="button" value="수정" onclick="goUpdate(${boardDTO.id})">
<input type="button" value="삭제" onclick="goDelete(${boardDTO.id})">
<input type="button" value="목록" onclick="goList()">
</th>
</tr>
</body>
<script>
const goList = () => {
const type = '${type}';
const q = '${q}';
const page = '${page}';
location.href = "/board/paging?page="+page +"&type="+type + "&q=" +q;
}
</script>
전체를 가져오기에는 너무 길기 떄문에 짧게 추가부분입니다
detail부분에서 > 목록 < 버튼을 누르게 되면 goList 함수가 실행되고
goLIst함수에서는 Controller로 잘 넘겨 받아온 model에 담긴 전 페이징 정보를 변수로 담아
location.href를 사용하여 파라미터 형식으로 Controller로 넘겨줍니다
- PagingSearch - Service (페이징 처리)
전날에 만들었던 pagingParam은 매개변수로 int page만 받지만
이번에 Search가 포함되면서 매개변수의 갯수가 늘어나 비슷하지만 다른
새메소드로 추가하게 되었습니다
리펙토링이 가능한 부분이 보이지만 그건 따로해보겠습니다
public PageDTO pagingSearchParam(int page,String type,String q) {
int pageLimit = 9;
한페이지에 보여줄 글 갯수
int blockLimit = 3;
하단에 보여줄 페이지 번호 갯수
Map<String, Object> pagingParams = new HashMap<>();
pagingParams.put("q", q);
pagingParams.put("type",type);
q = 검색값
type = 검색 타입 (제목 , 내용 , 작성자 중택1)
의 정보를 매개변수로 Controller에서 받아왔습니다
Mapper에는 2가지 변수를 넣어줄 수 없기 떄문에 압축을위해
Map에 각각의 키와 벨류로 값을 넣어 하나로 합쳐줍니다
int boardCount = boardRepository.boardSearchCount(pagingParams);
전체 글 갯수 조회
이부분은 전날의 페이징 count 메서드와 조금 기능이 다름 Mapper확인 필요
int maxPage = (int) (Math.ceil((double)boardCount / pageLimit));
전체 페이지 갯수 계산
int startPage = (((int)(Math.ceil((double) page / blockLimit))) - 1) * blockLimit + 1;
시작 페이지 값 계산(1, 4, 7, 10 ~~)
int endPage = startPage + blockLimit - 1;
마지막 페이지 값 계산(3, 6, 9, 12 ~~)
if (endPage > maxPage) {
endPage = maxPage;
}
전체 페이지 갯수가 계산한 endPage 보다 작을 때는 endPage 값을 maxPage 값과 같게 세팅
PageDTO pageDTO = new PageDTO();
pageDTO.setPage(page);
pageDTO.setMaxPage(maxPage);
pageDTO.setEndPage(endPage);
pageDTO.setStartPage(startPage);
이렇게 매개변수로 받은 정보를 토대로한
paging 데이터를 DTO에 담아 리턴합니다
return pageDTO;
}
- boardSearch - Repository & Mapper (전체글 갯수확인)
public int boardSearchCount(Map<String, Object> pagingParams) {
return sql.selectOne("Board.searchCount", pagingParams);
위에서 먼저 페이징처리 해야할 게시글의 갯수를 알아오기 위해
보냈던 pagingParams를 처리하는 Repository입니다
}
<select id="searchCount" parameterType="java.util.HashMap" resultType="Integer">
select count(id) from board_table where ${type} like concat('%', #{q}, '%')
게시글의 갯수를 알아보는데 Map의 정보가 필요한 이유입니다
검색에 맞는 글의 갯수만 알아야하기 때문인데요
Map에 담은 type 여기서는 (boardTitle , boardContents , boardWriter)중 하나이기 때문에
where의 비교 조건으로 사용되고
Map 담긴 다른 q는 검색어를 담은 값이기 때문에 MySQL쿼리문 양식에 맞춰 작성합니다
하지만 저희는 Mapper를 통하여 쿼리문을 DB로 넘기기 때문에
'%#{q}%' 로 작성하면 잘 전달되지 않는 오류가 있으므로
concat을 사용하여 각각 따로 이어 붙여 전달해줍니다
</select>
- PagingSearch - Service (리스트 뽑기)
public List<BoardDTO> searchList( int page,String type,String q) {
int pageLimit = 9;
한페이지에 보여줄 글의 갯수
int pagingStart = (page-1) * pageLimit;
한페이지에 보여줄 글 시작점
1페이지라면 0 부터시작 , 2페이지라면 9부터 시작 이렇게 끊어준다
Map<String, Object> pagingParams = new HashMap<>();
pagingParams.put("start", pagingStart);
pagingParams.put("limit", pageLimit);
계산이 끝난 페이지글 제한을 위한 변수를 담아줍니다
pagingParams.put("q", q);
pagingParams.put("type",type);
검색 조건에 맞는 정보를 검색해야하기에 함께 Map으로 담아가져갑니다
여기서도 위메소드의 페이징 처리와 동일하게 MySQL로 전달해야하기 때문에
데이터 압축을 위해 Map에 담아주는 작업을 진행합니다
하지만 시작과 끝을 정하여 보여줄 글 갯수를 지정하는 방식이기 때문에
약간의 수학계산이 필요합니다
List<BoardDTO> boardDTOList = boardRepository.searchList(pagingParams);
Repository를 통해 Mapper로 이동합니다
return boardDTOList;
}
- boardSearch - Repository , Mapper (마무리)
public List<BoardDTO> searchList(Map<String, Object> pagingParams) {
return sql.selectList("Board.search", pagingParams);
그냥 Map을 전달하기 위한 Repository 앞에서 고생을 너무했다
}
<select id="search" parameterType="java.util.HashMap" resultType="board">
select * from board_table where ${type} like concat('%', #{q} , '%')
type을 조건으로 가져온 q 검색값이 포함되는 List를 출력합니다
Mapper로 전달하는 like 쿼리문의 사용법이 잘 작동하지 않기 때문에
concat으로 따로 이어 붙여 전달해주었습니다
order by id asc limit #{start}, #{limit}
오름차순 정렬을하여 List에 담고 limt을 이용하여
시작부분과 최대갯수를 정해 출력받습니다
</select>
'나의 수업일지' 카테고리의 다른 글
인천 일보 아카데미 55일차 -회원제 게시판 만들기 - 회원가입 - 사진 프리뷰 / 회원가입 - 비밀번호 정규식 제약 조건 / 로그인 경고문 (0) | 2023.05.15 |
---|---|
인천 일보 아카데미 54일차 -로그인 게시판- 틀만들기 (저장용 내용X) / 업로드 파일 이름 출력 (0) | 2023.05.10 |
인천 일보 아카데미 53일차 -비로그인 게시판- List 페이징 기능 (0) | 2023.05.09 |
인천 일보 아카데미 53일차 - 비로그인 게시판 - 댓글 기능 (0) | 2023.05.08 |
인천 일보 아카데미 52일차 -1- 이미지 업로드 / detail에 이미지 보여주기 (0) | 2023.05.07 |