20220921 [Spring Boot, H2DB, MyBatis]

Yeoonnii·2022년 9월 21일
0

TIL

목록 보기
31/52
post-thumbnail

Interceptor

Controller에서 공통되는 작업이나 부가기능을 모아 관리하기 위해 aop 를 사용한다고 배웠었다
➡️ aop와 비슷하게 공통 프로세스를 처리하는 Interceptor 사용해보기

Interceptor, AOP 개념 및 차이점

Interceptor 사용하기

🌎참고링크

  1. Interceptor를 지원하는 @Component 클래스를 생성한다
  2. HandlerInterceptor interface를 구현해야 한다
    (또는 HandlerInterceptorAdapter 상속)
    ➡️ HandlerInterceptor 를 CustomHandlerInterceptor 에서 구현
  3. Interceptor를 사용하기 위한 3가지 추상 메서드를 구현한다.
    • preHandle() : 컨트롤러에 요청을 보내기 전 작업을 수행시 사용
      이 메서드가 클라이언트에게 응답을 반환하려면, true를 리턴해야함
    • postHandle() : 클라이언트에 응답을 보내기 전 작업을 수행시 사용
    • afterCompletion() : 요청 및 응답 완료 후 작업을 수행시 사용

InterceptorConfig.java

@Configuration = 서버를 구동할때 미리 자동으로 수행된다
➡️ application.properties(=환경설정)에 등록된것과 같다

WebMvcConfigurer 인터페이스를 상속받아 오버라이드 메서드 addInterceptors를 사용
1. 인터셉터 클래스를 등록, 필터할 url주소를 설정, 제거, 추가 하기 위해 HandlerInterceptor type의 Interceptor 객체가 필요하다
registry.addInterceptor(직접 생성한 인터셉터 객체)
➡️ 생성된 CustomHandlerInterceptor@Autowired하여 사용

addPathPatterns = 사용할 페이지
excludePathPatterns = 필터(제외)할 페이지

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    
    // 생성한 handler 사용
    @Autowired
    CustomHandlerInterceptor handlerInterceptor;

    // 인터셉터 클래스를 등록, 필터할 url주소를 설정, 제거, 추가 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // registry.addInterceptor(직접 생성한 인터셉터 객체)
        registry.addInterceptor( handlerInterceptor )
        .addPathPatterns("/**")  // 전체 페이지 사용(기록된다)
        // 필터할 주소 설정 (사용하지 않을 페이지 설정)
        .excludePathPatterns("/seller/**", "/admin/**", "/member/**", "/item_image/**" );
        // item_image도 주소로 취급되기 때문에 지워준다
    }
}

CustomHandlerInterceptor.java

생성후 어노테이션 = @Component 명시
CustomLoginSuccessHandler.java / CustomLogoutSuccessHandler.java
➡️ @Component 명시해줘야 한다

postHandle은 파라미터에 ModelAndView가 있으니
외부에서 들어오는 데이터를 저장/공유 가능하다는것을 예측할 수 있다
= Controller 진입 후 view 표시전에 수행

preHandle은 model이 없어 데이터를 저장/공유할 수는 없지만
Controller에서 이루어지는 공통작업들을 Controller보다 먼저 수행하도록 한다

@Component
@Slf4j
public class CustomHandlerInterceptor implements HandlerInterceptor {
    final String format = "INTERCEPTOR => {}";

    // 컨트롤러 진입 후 view 표시전에 수행
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        log.info(format, "postHandle");
        
    }
    
    // 컨트롤러 진입 전에 수행
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
        // 1. 세션 가져오기
        HttpSession httpSession = request.getSession();

        if( request.getQueryString() == null ){
            httpSession.setAttribute("CURRENT_URL", request.getContextPath() );
        }
        else {
            httpSession.setAttribute("CURRENT_URL", request.getContextPath() + request.getServletPath() + "?" + request.getQueryString());
        }

        log.info(format, "preHandle");
        // 하위 3개 내용으로 url판별 가능
        // http://127.0.0.1:8080/BOOT1/
        // InterceptorConfig.java에서 제외한 기록은 출력되지 않는다
        log.info(format, request.getContextPath()); //출력확인 결과 => /BOOT1
        log.info(format, request.getServletPath()); //출력확인 결과 => /
        log.info(format, request.getQueryString()); //출력확인 결과 => null *?r가 없으면 null, ?가 있으면 no=14등 해당 번호출력
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }
    
}

Application.java

Application.java @ComponentScan에 handler추가
➡️ "com.example.handler"

💡 @Autowired 사용하려면 application에 등록을 해야된다

기본적으로는 @Autowired@Bean으로 객체 생성후
Application에 등록하고 사용하는게 올바른 방법이다

SecurityConfig.java에서
customLoginSuccessHandler/CustomLogoutSuccessHandler 사용하려면
@Autowired 해서 사용해야 하는데

@Autowired
CustomLoginSuccessHandler customLoginSuccessHandler;

@Autowired
CustomLogoutSuccessHandler customLogoutSuccessHandler;

CustomLoginSuccessHandler.java / CustomLogoutSuccessHandler.java 에서
@Autowired 사용을 위해 어노테이션 = @Component를 명시해 주어야 한다
@Component명시해주어야 @Autowired 사용 가능하다


서버 실행시 Interceptor 출력결과

서버 실행시 preHandlepostHandle 핸들 순서로 실행되는것을 터미널에서 확인할 수 있다
⇒ 메인페이지 접속시 터미널 출력결과

excludePathPatterns를 사용하여 제외했던 페이지인 seller 페이지에 들어가면
Interceptor가 필터되어 터미널창에 출력되지 않는것을 확인할 수 있다
⇒ 판매자 페이지 로그인 후 접속시 터미널 출력결과


고객용 메인페이지 물품조회

ItemMapper.java

고객용 물품조회 목록 생성

public List<ItemDTO> selectItemList();

itemMapper.xml

<!-- 고객용 물품목록, 12개, 가격낮은순 -->
    <select id="selectItemList" resultType="com.example.dto.ITEMDTO">
        SELECT * FROM(
            SELECT I.*, ROW_NUMBER() OVER(ORDER BY PRICE ASC) ROWN FROM ITEMTBL I 
        ) WHERE ROWN BETWEEN 1 AND 12 ORDER BY ROWN ASC;
    </select>

HomeController.java

  1. 홈페이지 로드시 imapper에서 조회한 데이터 가져오기
    List<itemDTO> = imapper.selectitemList();
  2. 가져온 데이터를 모델에 담아 임의의 키값과 함께 html로 전달
    model.addAttribute("list", list);

home.html

반복문 실행하여 가져온 물품데이터를 화면에 출력한다

<tr th:each="obj, idx : ${list}">
	<td th:text="${obj.name}"></td>
	<td th:text="${obj.no}"></td>
	<td th:text="${obj.content}"></td>
	<td th:text="${obj.price}"></td>
...

고객용 메인페이지 이미지조회

물품에 등록된 여러개의 이미지 중 대표이미지만 조회

  • ItemImageMapper 사용하여 이미지 불러오기
  • HomeController 에서 물품번호obj.no를 보내줬어야하고,
    가져올 아이템 이미지 번호를 받아와야한다
  • 아이템 이미지 주소url 생성시
    ⇒ 해당 번호가 있는경우는 url 생성, 번호가 없는경우 생성하지 않는다

home.html

이미지 클릭시 해당 물품번호 보내주기 ➡️ obj.no
home.html에서 이미지 url 출력하기

<img th:src="@{ ${obj.image} }" style="width: 50px; height:50px" /></td>

HomeController.java

반복문 이용하여 이미지 url넣기

  • 아이템에 이미지가 첨부된 경우 = 해당이미지 출력
  • 이미지가 첨부되지 않은 경우 = 기본이미지 출력
// 아이템 이미지 불러오기
    // th:src="@{/item_image(no=${obj.no})}"
    @GetMapping(value = "/item_image")
    public ResponseEntity<byte[]> imageGET(
            @RequestParam(name = "no") Long no) throws IOException {

        // 아이템 이미지 번호가 존재하는 경우
        if (no > 0L) {
            ItemImageDTO item = iimapper.selectImageOne(no);
            if (item.getImagesize() > 0L) { // 이미지 파일이 존재하는 경우
                // 타입설정 png인지 jpg인지 gif인지
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(
                        MediaType.parseMediaType(item.getImagetype()));
                // 실제이미지데이터, 타입이포함된 header, status 200
                ResponseEntity<byte[]> response = new ResponseEntity<>(
                        item.getImagedata(), headers, HttpStatus.OK);
                return response;
            } else { // 이미지 파일이 존재하지 않는경우 = default이미지 설정
                InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
                        .getInputStream();
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.IMAGE_JPEG);
                // 실제이미지데이터, 타입이포함된 header, status 200
                ResponseEntity<byte[]> response = new ResponseEntity<>(
                        is.readAllBytes(), headers, HttpStatus.OK);
                return response;
            }
        } else { //아이템 이미지 번호가 존재하지 않는경우 = default이미지 설정
            InputStream is = resourceLoader.getResource("classpath:/static/image/noimage.jpg")
                    .getInputStream();
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.IMAGE_JPEG);
            // 실제이미지데이터, 타입이포함된 header, status 200
            ResponseEntity<byte[]> response = new ResponseEntity<>(
                    is.readAllBytes(), headers, HttpStatus.OK);
            return response;
        }
    }

ItemImageMapper.java

물품번호 전달시 대표이미지 1개를 이미지 번호를 반환

public ItemImageDTO selectImageNoOne(Long itemno);

ItemDTO.java

💡 DTO vs entity

ItemDTO에 임시변수 생성 = 대표이미지 url을 보관할 임시변수
DTO는 entity와 다르기 때문에 테이블내에 존재하지 않는 컬럼을 필드로 가질 수 있다
= DTO는 데이터 전달용 객체일 뿐

private String image;
// set + Image=변수명(String) or get + Image=변수명()

HomeController.java

홈화면 로드시 아이템 이미지 불러오기

  • 물품번호 전송하기 ➡️ iimapper.selectImageNoOne(item.getNo())
  • 가져온 여러개의 ItemDTO를 반복문 이용하여 itemimage url 생성 ➡️ for(ItemDTO item : list)
  • Controller에서 html= view 넘어가는 과정에서 view에 넘겨줄 값을 model에 담아 전달한다
    ➡️ model.addAttribute("문자로 되어있는 전달할 임의의 키값", 실제 전달될 값);
//홈페이지 로드시
@GetMapping(value = { "/", "/home", "/home.do" })
	// UserDetails에서 반환된 리턴값 User
    public String homeGET(@AuthenticationPrincipal User user, Model model) { 
        List<ItemDTO> list = imapper.selectItemList();
        // 넘어온 데이터 확인용
        System.out.println(list.toString());

        // 수동으로 이미지URL을 포함시킴
        for (ItemDTO item : list) {
            // 물품번호 전송하면 itemimage가 온다
            ItemImageDTO obj = iimapper.selectImageNoOne(item.getNo());
            item.setImage("/item_image?no=" + obj.getNo());
        }
        
        model.addAttribute("user", user);
        model.addAttribute("list", list);

        return "home";
    }

물품 주문하기

home.html

이미지 클릭하면 주문페이지로 이동
로그인하지 않은 상태에서 이동해야하니 HomeController에 생성해준다

<td>
	<a th:href="@{/content.do( item.no=${obj.no} )}">
		<img th:src="@{ ${obj.image} }" style="width: 50px; height:50px" />
	</a>
</td>

HomeController.java

  • 물품번호를 이용해서 클릭한 물품의 정보만 가져오기
  • 해당 물품에 등록된 이미지 전부 가져오기
  • return content = 주문페이지 상세화면 만들기
    @GetMapping(value = {"/content.do"}) 
    public String contentGET(
        Model model,
        @RequestParam(name = "itemno") Long no){

            // 1. 물품번호를 이용해서 정보 가져오기
            ItemDTO item = imapper.selectItemOne(no);
            // 2. 물품관련 이미지 전체 가져오기
            List<ItemImageDTO> list = iimapper.selectImageList(no);

            System.out.println("item => " + item.toString());
            System.out.println("list => " + list.toString());

            model.addAttribute("item", item);
            model.addAttribute("list", list);

            return "content";
        }

content.html

물품 주문페이지 생성

  • HomeController 에서 item으로 넘겨준 물품정보는 1개
  • 1개의 물품정보를 입력하니 반복문 사용하지 않음
  • HomeController 에서 list로 이미지는 1개 이상이니 입력시 반복문 사용
  • 이미지가 있는경우/비어있는경우 2가지 조회결과로 나누어 이미지 url생성
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">

<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></title>
</head>

<body>
    <h3>물품상세</h3>

	<!-- homecontroller 에서 item으로 넘겨준 정보 입력! 1개의 정보이니 반복문사용X -->
    <form th:action="@{/customer/order.do}" method="post">
        <p th:text="${item.no}"></p>
        <p th:text="${item.price}"></p>
        <p th:text="${item.content}"></p>
        <p th:text="|${item.price} 원|"></p>

        <!-- 이미지 여러개 반복문으로 입력 -->
        <!-- 이미지가 비어있지 않은경우 -->
        <div th:if="${not #lists.isEmpty(list)}">
            <th:block th:each="item, idx : ${list}">
                <img th:src="@{/item_image(no=${item.no})}" style="width:200px;" />
            </th:block>
        </div>
        <!-- 이미지가 비어있는경우 -->
        <div th:if="${#lists.isEmpty(list)}">
            <img th:src="@{/item_image(no=0)}" style="width:200px;" />
        </div>

        <select>
            <th:block th:each="tmp, idx : ${#numbers.sequence(1,100)}">
                <option th:text="${tmp}" th:value="${tmp}"></option>
            </th:block>
        </select>

        <input type="submit" value="주문하기" />
    </form>
</body>
</html>

주문하기 버튼 클릭시 고객 권한을 가진 사용자만 주문이 가능하도록 하고
고객 권한을 확인하여 고객 권한이 없으면 로그인 페이지로 이동한다
로그인 페이지로 이동하여 로그인 후 원래 있던 물품 상세페이지로 이동하게 한다

InterceptorConfig.java

로그인 http://127.0.0.1:8080/BOOT1/member/login.do 후에
원래 있었던 물품 상세페이지로 돌아가야하니 excludePathPatterns 제외할 주소에 /member/** 추가

쿼리있는경우 없는경우 구분하여 주소 생성

if( request.getQueryString() == null ){
            httpSession.setAttribute("CURRENT_URL", request.getContextPath() );
        }
        else {
            httpSession.setAttribute("CURRENT_URL", request.getContextPath() + request.getServletPath() + "?" + request.getQueryString());
        }

CustomHandlerInterceptor.java

preHandle에서 url 판별을 위해 log를 기록해준다
InterceptorConfig.java에서 excludePathPatterns사용하여 제외한 기록은 출력되지 않는다

url에서 ?뒤의 주소가 없으면 null, ?뒤의 추가 주소가 있으면 no=14등 해당 번호(쿼리)출력

// 하위 3개 내용으로 url판별 가능
// ex. url => http://127.0.0.1:8080/BOOT1/
        log.info(format, request.getContextPath()); //출력확인 결과 => /BOOT1
        log.info(format, request.getServletPath()); //출력확인 결과 => /
        log.info(format, request.getQueryString()); //출력확인 결과 => null 
  		
  • getRequestURL()메소드는 쿼리문자열을 잘라버리고 출력함
    아래에 굵게 표현된 쿼리문자열 부분은 getRequestURL() 메소드로는 가져올 수 없다
    ➡️ http://localhost/community/board.jsp?bid=free&page=1
  • getQueryString() 메소드는 Get방식으로 넘어온 쿼리문자열을 구하기 위한 request 객체의 메소드, 쿼리문자열이 없을때는 null을 리턴
  • 현재 페이지의 URL과 쿼리문자열까지 모두 구하기 위해서는 getRequestURL() + getQueryString() 두개의 메소드로 구해진 결과 값을 더해주면 된다
// 예시
String url = request.getRequestURL().toString();
if ( request.getQueryString() != null )
url = url + "?" + request.getQueryString();
out.println(url);

CustomLoginSuccessHandler.java

로그인 세션 확인
고객의 세션정보를 확인하기 위해 세션을 가져온다 httpSession 이용

...
if(role.equals("CUSTOMER")){
            // 이동하고자 하는 위치 작성시 getContextPath 사용
            // request.getContextPath() = /BOOT1
            
            // 세션에서 꺼냄
            String currentUrl = (String) httpSession.getAttribute("CURRENT_URL");
            if(currentUrl == null){ //세션정보 없음
                response.sendRedirect( 
                    request.getContextPath() + "/customer/home.do" );
            }
            else { // 세션정보가 있다면 가장 마지막 세션기록 URL로 이동
                response.sendRedirect(currentUrl);
            }
...

물품 주문하기 로직

주문은 로그인 처리가 되어 customer권한을 확보해야 가능하다

content.html 에서
<form th:action="@{/customer/order.do}" method="post">에서 /customer/order.do로 이동시 권한 확보 여부에 따라 이동한다

  1. /customer/order.doInterceptorConfig.java에서excludePathPatterns(=제외된 주소)에 포함되어있지 않으니 바로 CustomHandlerInterceptor.java로 이동
  2. CustomHandlerInterceptor.java에서 컨트롤러 진입전에 수행되는 preHandle 실행됨 HttpSession httpSession = request.getSession();으로 세션 가져와서 확인
  3. CustomLoginSuccessHandler.java에서 로그인 세션의 권한 확인
  4. 로그인 세션의 권한에 따라 이동할페이지 지정
    (해당 권한이 세션 있는경우 가장 마지막 세션기록 URL로 이동 currentUrl , 없는경우 /customer/home.do로 이동)
  5. SecurityConfig.java에서 페이지별 접근권한 설정 해뒀었음
    주문버튼 클릭시 이동할 링크는 /customer/order.do이기 때문에 고객권한만 접근 가능

0개의 댓글