스프링으로 쇼핑몰을 만들어보자19 - 작가등록 기능 구현 2
기능구현
- 회원가입(주소API연동, 이메일 인증, ajax를 사용하여 아이디 중복검사)
- 관리자 페이지(인터셉터 적용, 상품관리, 회원관리)
- 업로드(이미지 등록, 수정, 삭제)
- 검색
- 페이징
- 구매(장바구니, 포인트사용)
- 댓글(등록,수정,삭제)
- 중간에 막혔거나 에러 해결 못하겠으면 ybnr92@gmail.com 으로 문의
목표
- 제어계층(AuthorController.java)
- 뷰(View)로부터의 요청 처리하는 url 맵핑 메서드 작성
- 프레젠테이션 계층(authorEnroll.jsp)
- 작가 등록에 사용될 데이터를 입력 및 전송할 수 있도록 작성
순서
- 서비스 단계 테스트
- url 매핑 메서드 추가(AdminController.java)
- View작성(authorEnroll.jsp)
- 버튼 작동 스크립트 추가(authorEnroll.jsp)
- 작가 등록 성공 경고창(authorManage.jsp)
- 테스트
AuthorService.java 테스트
- 지난 포스팅에서
AuthorMapper.java
에 구현된 메서드에 대해서는 테스트를 진행하였고AuthorService.java
의 메서드에 대해서는 작성은 하였지만 테스트를 하지 않았다. 그에 대한 테스트도 진행하려 한다. - 하나하나 모두 테스트 하는것이 많이 번거롭지만 오류가 발생했을 때 오히려 시간을 줄여주는 큰 역할을 하기 때문에 되도록이면 기능을 구현했을 때 바로바로 테스트를 해주는 것이 좋다. 그리고 항상 테스트 코드를 작성하는 습관을 들여야 한다.
src/test/java
경로에com.store.service
패키지를 생성한 뒤AuthorServiceTests.java
클래스를 생성한다.
package com.store.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.store.model.AuthorVO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file/src/main/webapp/WEB-INF/spring/root-context.xml")
public class AuthorServiceTests {
/* AuthorService 의존성 주입 */
@Autowired
private AuthorService service;
@Test
public void authorEnrollTest() throws Exception {
AuthorVO author = new AuthorVO();
author.setNationId("01");
author.setAuthorName("테스트");
author.setAuthorIntro("테스트 소개");
service.authorEnroll(author);
}
}
- 위 처럼 그대로 따라하면 에러가 날 것이다. 아래 처럼 바꿔주자.
package com.store.service;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.store.model.AuthorVO;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml") // 수정
public class AuthorServiceTests {
/* AuthorService 의존성 주입 */
@Autowired
private AuthorService service;
@Test
public void authorEnrollTest() throws Exception {
AuthorVO author = new AuthorVO();
author.setNationId("01");
author.setAuthorName("테스트");
author.setAuthorIntro("테스트 소개");
service.authorEnroll(author);
}
}
url 매핑 메서드 추가(AdminController.java)
- 먼저
AuthorService.java
인터페이스를 의존성 주입해주는 코드를 추가한다.
@Controller
@RequestMapping("/admin")
public class AdminController {
private static final Logger logger = LoggerFactory.getLogger(AdminController.class);
@Autowired
private AuthorService authorService;
- url이
authorEnroll.do
인 POST방식의 url매핑 메서드를 추가한다. - 파라미터로는
BoardVO
타입 변수RedirectAttributes
타입 변수를 추가하였다.
BoardVO
객체는 뷰가 전송하는 작가 관련 데이터를 받기 위해서이다.RedirectAttributes
객체는 해당 메서드가 종료된 뒤 리다이렉트 방식으로 다른 페이지로 전송할 때 성공 메시지를 전송하기 위해서 추가했다.(리다이렉트 방식으로 이동할 때 데이터 전송을 위해 사용되는 Model 객체라고 생각하면 됨)
package com.store.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.store.model.AuthorVO;
import com.store.service.AuthorService;
@Controller
@RequestMapping("/admin")
public class AdminController {
private static final Logger logger = LoggerFactory.getLogger(AdminController.class);
@Autowired
private AuthorService authorService;
/* 관리자 메인 페이지 이동 */
@RequestMapping(value = "main", method = RequestMethod.GET)
public void adminMainGET() throws Exception {
logger.info("관리자 페이지 이동");
}
/* 상품 관리 페이지 접속 */
@RequestMapping(value = "goodsManage", method = RequestMethod.GET)
public void goodsManageGET() throws Exception {
logger.info(">>>>>>>>>> 상품 관리 페이지 접속");
}
/* 상품 등록 페이지 접속 */
@RequestMapping(value = "goodsEnroll", method = RequestMethod.GET)
public void goodsEnrollGET() throws Exception {
logger.info(">>>>>>>>>> 상품 등록 페이지 접속");
}
/* 작가 관리 페이지 접속 */
@RequestMapping(value = "authorManage", method = RequestMethod.GET)
public void anthorManageGET() throws Exception {
logger.info(">>>>>>>>>> 작가 관리 페이지 접속");
}
/* 작가 등록 페이지 접속 */
@RequestMapping(value = "authorEnroll", method = RequestMethod.GET)
public void authorEnrollGET() throws Exception {
logger.info(">>>>>>>>>> 작가 등록 페이지 접속");
}
/* 작가등록 */
@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
}
}
- 추가한 메서드(작가등록)구현부에 먼저 해당 메서드에 들어온 기록과 뷰로부터 전달받은 데이터를 확인하기 위한 로그 코드를 추가하였다.
/* 작가등록 */
@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
logger.info("authorEnroll : " + author);
}
- 작가 등록 쿼리를 수행하는 mapper메서드를 호출하는
AuthorService.java
의authorEnroll
메서드를 호출한다. 뷰로부터 전달받은 등록할 작가 정보가 담긴BoardVO
변수를 매개변수로 작성한다.
/* 작가등록 */
@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
logger.info("authorEnroll : " + author);
authorService.authorEnroll(author); // 작가 등록 쿼리 수행
}
- 리다이렉트 방식으로 작가 목록 페이지로 이동하도록 리턴값을 추가한다.
/* 작가등록 */
@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
logger.info("authorEnroll : " + author);
authorService.authorEnroll(author); // 작가 등록 쿼리 수행
return "redirect:/admin/authorManage";
}
- 작가등록이 성공적으로 완료되었음을 알리는 데이터를 전송해주는 코드를 추가한다.
- 경고창 메시지에 등록된 작가이름을 표시하기 위해 동록이 완료된 작가이름 데이터를 전송하였다. 뷰로 전송된 데이터가 일회성으로 사용되도록
addFlashAttribute
메서드를 사용하였다. - 작가 등록 성공 후 이동할 페이지가 작가관리 페이지인데 해당 페이지가 로드될 때마다 서버로부터 전송받은 성공여부데이터를 존재 여부를 체크하여 존재할 시 작가 등록에 성공하였다는 경고창을 뜨게 할 것이다. 그런데 만약 서버로부터 전송된 데이터가 계속 남아있다면 해당 경고창은 계속 나타날 것이다. 이러한 일을 방지하고자 데이터가 일회성으로 사용될 수 있도록
addFlashAttribute
메서드를 사용하였다.
/* 작가등록 */
@RequestMapping(value = "authorEnroll.do", method = RequestMethod.POST)
public String authorEnrollPOST(AuthorVO author, RedirectAttributes rttr) throws Exception {
logger.info("authorEnroll : " + author);
authorService.authorEnroll(author); // 작가 등록 쿼리 수행
rttr.addFlashAttribute("enroll_result", author.getAuthorName()); // 등록 성공 메시지(작가이름)
return "redirect:/admin/authorManage";
}
View 작성(/admin/authorEnroll.jsp)
- class 속성 값
admin_content_subject
인<div>
태그 바로 아래에 작가 정보를 작성하고 전송할 수 있는 버튼이 있는 태그 코드들을 추가하였다.
<%@ 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="../resources/css/admin/authorEnroll.css">
<script
src="https://code.jquery.com/jquery-3.4.1.js"
integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU="
crossorigin="anonymous"></script>
</head>
</head>
<body>
<div class="wrapper">
<div class="wrap">
<!-- gnv_area -->
<div class="top_gnb_area">
<ul class="list">
<li><a href="/main">메인 페이지</a></li>
<li><a href="/member/logout.do">로그아웃</a></li>
<li>고객센터</li>
</ul>
</div>
<!-- top_subject_area -->
<div class="admin_top_wrap">
<span>관리자 페이지</span>
</div>
<!-- contents-area -->
<div class="admin_wrap">
<!-- 네비영역 -->
<div class="admin_navi_wrap">
<ul>
<li >
<a class="admin_list_01" href="/admin/goodsEnroll">상품 등록</a>
</li>
<li>
<a class="admin_list_02" href="/admin/goodsManage">상품 관리</a>
</li>
<lI>
<a class="admin_list_03" href="/admin/authorEnroll">작가 등록</a>
</lI>
<lI>
<a class="admin_list_04" href="/admin/authorManage">작가 관리</a>
</lI>
<lI>
<a class="admin_list_05">회원 관리</a>
</lI>
</ul>
</div>
<div class="admin_content_wrap">
<div class="admin_content_subject"><span>작가 등록</span></div>
<!-- 작가 정보 작성 및 전송 코드 추가 -->
<div class="admin_content_main">
<form action="/admin/authorEnroll.do" method="post" id="enrollForm">
<div class="form_section">
<div class="form_section_title">
<label>작가 이름</label>
</div>
<div class="form_section_content">
<input name="authorName">
</div>
</div>
<div class="form_section">
<div class="form_section_title">
<label>소속 국가</label>
</div>
<div class="form_section_content">
<select name="nationId">
<option value="none" selected>=== 선택 ===</option>
<option value="01">국내</option>
<option value="02">국외</option>
</select>
</div>
</div>
<div class="form_section">
<div class="form_section_title">
<label>작가소개</label>
</div>
<div class="form_section_content">
<input name="authorIntro" type="text">
</div>
</div>
</form>
<div class="btn_section">
<button id="cancelBtn" class="btn">취 소</button>
<button id="enrollBtn" class="btn enroll_btn">등 록</button>
</div>
</div>
<!-- 작가 정보 작성 및 전송 코드 추가 -->
</div>
<div class="clearfix"></div>
</div>
<!-- Footer 영역 -->
<div class="footer_nav">
<div class="footer_nav_container">
<ul>
<li>회사소개</li>
<span class="line">|</span>
<li>이용약관</li>
<span class="line">|</span>
<li>고객센터</li>
<span class="line">|</span>
<li>광고문의</li>
<span class="line">|</span>
<li>채용정보</li>
<span class="line">|</span>
</ul>
</div>
</div> <!-- class="footer_nav" -->
<div class="footer">
<div class="footer_container">
<div class="footer_left">
<img src="../resources/img/Logo.png">
</div>
<div class="footer_right">
(주) 이름없는 회사 대표이사 : OOO
<br>
사업자등록번호 : ooo-oo-ooooo
<br>
대표전화 : oooo-oooo(발신자 부담전화)
<br>
<br>
COPYRIGHT(C) <strong>yoonbitnara.github.io</strong> ALL RIGHTS RESERVED.
</div>
<div class="clearfix"></div>
</div>
</div> <!-- class="footer" -->
</div> <!-- class="wrap" -->
</div> <!-- class="wrapper" -->
</body>
</html>
조심해야 할 태그
- form 태그
- action 속성 값에 작가등록 기능을 수행하는 url을 작성하였고 해당 url 매핑 메서드가 POST방식이기 때문에 method 속성 값을 POST로 작성하였다.
<form action="/admin/authorEnroll.do" method="post" id="enrollForm">
</form>
- 작가 등록 기능을 수행하는 url 매핑 메서드가 작가 등록에 사용할 데이터를 AuthorVO객체를 파라미터로 전달 받기 때문에 AuthorVO클래스에 정의한 변수 이름과 authorEnroll.jsp에 정보가 입력될
<input>, <select>
태그의 name 속성 값이 일치하도록 작성해야 한다. 일치하지 않으면 데이터는 서버로 전송되지 않는다.
<input name="authorName">
<select name="nationId">
<input name="authorIntro" type="text">
- 소속 국가 정보를 입력할 때
<input>
을 사용하지 않고<select>
를 사용하였다. 국내, 국외 두 정보중 한 가지를 선택하도록 강제하기 위해<select>
를 사용하였다.
<select name="nationId">
<option value="none" selected>=== 선택 ===</option>
<option value="01">국내</option>
<option value="02">국외</option>
</select>
버튼 작동 스크립트 추가(authorEnroll.jsp)
- 취소 버튼과 작가등록 버튼 두개가 있다. 두 개의 버튼이 작동되도록 스크립트 코드를 작성하였다.
<script>
/* 등록버튼 */
$("#enrollBtn").click(function(){
$("#enrollForm").submit();
});
/* 취소버튼 */
$("#cancelBtn").click(function() {
location.href="/admin/authorManage";
});
</script>
- 취소 버튼을 누르면 작가 관리 페이지(authorManage.jsp)로 이동하도록 하였고 등록 버튼을 누르면 작가 등록 기능이 실행되도록 작성하였다.
작가 등록 성공 경고창(authorManage.jsp)
- 작가 등록 수행 후 작가 관리 페이지에 이동함과 동시에 등록이 성공하였음을 알리는 데이터(
enroll_result
)를 전송하였다. 이 데이터를 활용하여 성공을 알리는 경고창을 띄우는 코드를 작성한다. authorManage.jsp
하단에<script>
태그를 추가 후 페이지가 로드될 때 반드시 실행이 되는 익명 함수를 추가한다.
<script>
$(document).ready(function() {
});
</script>
- 함수 구현부에 서버에서 전송되는 데이터를 체크 후 존재 할 시 작가 등록 성공 메시지를 알리는 경고창을 띄운다.
$(document).ready(function() {
let result = "${enroll_result}";
checkResult(result);
function checkResult(result) {
if (result == "") {
return;
}
alert("작가'${enroll_result}' 님을 등록하였습니다.");
}
});
- 작성한 코드 그대로 사용하더라도 정상적으로 작동을 하지만
${enroll_result}
는 사용자가 작성한 값이 그대로 전송되기 때문에 XSS 공격과 같이 스크립트 코드를 주입시키는 웹사이트 공격에 취약할 수 있다. - 예를 들면
${enroll_result}
는 작가 등록 때 작성한 작가 이름인데 사용자가<script>alert('공격');</script>
을 작가 이름에 작성을 한다면 작가 관리 페이지에 alert문이 실행될 수 있다. - 이를 방지하기 위해 작가이름 작성을 할 때 유효성 검사를 통해 스크립트 코드를 작성하지 못하도록 할 수도 있다.
- 스크립트 코드가 입력되더라도 출력되는 값(${})에도 스크립트 코드가 실행이 되지 않도록 할 수 있는 방법이 있는데 JSTL의
<c:out>
을 사용하는 방법이다. <c:out>
는 변수의 내용을 출력할 때 사용되는 태그인데 해당 태그에 HTML문자를 escape하는 기능이 있기 때문이다.- 표현언어(EL)인
${enroll_result}
에<c:out>
을 적용시키고자 한다. 먼저 JSTL 라이브러리 코드를 추가한다.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
- 기존
${enroll_result}
를<c:out>
태그를 적용 한다.
// 변경 전
let result = "${enroll_result}";
// 변경 후
let result = '<c:out value="${enroll_result}"/>';
테스트
- 취소 테스트
- 취소 버튼을 눌러 정상적으로 작가 관리 페이지로 이동하는지 확인
- 등록 테스트
- 작가 정보 데이터베이스 등록
- 작가 관리 페이지 이동
- 등록 성공 경고창 확인
Leave a comment