나만의 프로젝트 배포하기 4일차

박세건·2023년 9월 12일
0

개인 프로젝트

목록 보기
4/15

유지보수성을 위해 나만의 어노테이션을 만들어보자

추가적으로 로그인한 Member 정보를 갖고오기위해서는 HttpSession에 접속해서 키값이 "member" 인 키값을 찾아서 대입시켜줘야하는데, 이렇게 로그인한 유저의 정보를 계속해서 가져올때마다 이런 코드를 작성하는것은 같은 코드가 반복되는 것으로 유지보수성이 떨어지게됩니다.
따라서 이를 해결하기위해서 메소드에 인자값으로 어노테이션만 넣어주면 로그인 Member를 갖고올 수 있는 나만의 어노테이션을 만들어보자

  • 먼저 해당 어노테이션 인터페이스를 만들어준다
package com.qkrtprjs.happyexercise.config.auth;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)  //이 어노테이션을 사용할 방식, 선언될 수 있는 장소 지정, 파라미터라면 파라미터로 사용가능
@Retention(RetentionPolicy.RUNTIME) //이 어노테이션의 라이프 사이클, 즉 언제까지 살아있을 것이냐
public @interface LoginMember {
}
  • 어노테이션 사용할 방식과 라이프 사이클을 지정해주고 이제 이 어노테이션 관련 설정을 해줄 ArgumentResolver(HandlerMethodArgumentResolver 인터페이스를 상속받는)클래스를 만들어 준다.
package com.qkrtprjs.happyexercise.config.auth;

import com.qkrtprjs.happyexercise.config.auth.dto.SessionMember;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpSession;

@RequiredArgsConstructor
@Component
public class LoginMemberArgumentResolver implements HandlerMethodArgumentResolver {
    private final HttpSession session;

    //컨트롤러 메서드의 특정 파라미터를 지원하는지 판단, 즉 잘 사용했는지 판단

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean isLoginMemberAnnotation = parameter.getParameterAnnotation(LoginMember.class) != null;  //파라미터에 @LoginMember 어노테이션이 붙어있고
        boolean isMemberClass = SessionMember.class.equals(parameter.getParameterType()); //파라미터 클래스 타입이 SessionMember일 경우
        return isLoginMemberAnnotation && isMemberClass;    // true 리턴
    }


    //파라미터에 전달할 객체 생성
    //여기서는 HttpSession의 정보를 갖고와야한다
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return session.getAttribute("member");
    }
}

이렇게 어노테이션과 어노테이션 설정이 끝났다면 HandlerMethodArgumentResolver는 항상 WebMvcConfigurer의 addArgumentResolver() 메소드를 통해 추가되어야합니다.

package com.qkrtprjs.happyexercise.config;

import com.qkrtprjs.happyexercise.config.auth.LoginMemberArgumentResolver;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final LoginMemberArgumentResolver loginMemberArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginMemberArgumentResolver);
    }
    //HandlerMethodArgumentResolver는 항상 WebMvcConfigurer의 addArgumentResolvers()를 통해서 추가되어야한다!
    //즉 어노테이션을 만들어줄때에 WebMvcConfigurer에 의해서 추가되어야 한다는 것을 기억하자

}

이렇게 모든 설정을 완료했으면

    @GetMapping("/")
    private String index(Model model,@LoginMember SessionMember loginMember) {
        model.addAttribute("loginMember", loginMember);
        return "index";
    }

이렇게 어노테이션만 파라미터로 넘겨주어도 자동적으로 HttpSession에서 "memeber" 로 저장된 객체가 저장되게 됩니다!

js를 통한 회원 삭제

이전에 form형태를 갖고 회원 삭제를 진행했다면 이번에는 js를 통한 ajax로 회원삭제를 진행해보자. 추가로 수정 작업도 진행하려 했지만 OAuth2.0을 사용한 로그인과 회원가입만 가능하기때문에 수정은 불가하고 삭제만 가능하도록 설정

  • controller
    @DeleteMapping("/api/member/{id}")
    private Long delete(@PathVariable Long id) {
        memberService.delete(id);
        return id;
    }
  • service
    @Transactional
    public void delete(Long id) {
        //삭제할때에는 항상 그 값이 존재하는지 확인하고 삭제하는 과정을 거치자!
        Member member = memberRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("해당 회원이 존재하지 않습니다!"));
        memberRepository.delete(member);
    }

수정이나 삭제 조회시에는 항상 내가 찾고하는 것이 존재하는지를 판단한 후에 작업을 실행하도록 진행하자

  • 삭제 테스트 실행
package com.qkrtprjs.happyexercise.controller;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.qkrtprjs.happyexercise.dto.MemberSaveRequestDto;
import com.qkrtprjs.happyexercise.member.Member;
import com.qkrtprjs.happyexercise.member.MemberRepository;
import com.qkrtprjs.happyexercise.member.Role;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.xmlunit.util.Mapper;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WithMockUser(roles = "USER")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MemberApiControllerTest {

    @LocalServerPort
    private int port;

    @Autowired
    private MockMvc mvc;

    @Autowired
    private MemberRepository memberRepository;


    @After
    public void tearDown() throws Exception {
        memberRepository.deleteAll();
    }

    @Test
    public void member_등록() throws Exception {
        //given
        String name = "qkrtprjs";
        String email = "qkrtprjs456";
        String platform = "naver";
        String picture = "picture";
        MemberSaveRequestDto dto = MemberSaveRequestDto.builder()
                .name(name)
                .email(email)
                .platform(platform)
                .picture(picture)
                .build();
        String url = "http://localhost:" + port + "/api/member";
        //when
        mvc.perform(post(url)
                        .contentType(MediaType.APPLICATION_JSON)    //보낼 형식
                        .content(new ObjectMapper().writeValueAsString(dto)))   //보낼 데이터, ObjectMapper로 문자열 JSON 형태로 변환
                .andExpect(status().isOk())
                .andDo(print());

        //then
        Member member = memberRepository.findAll().get(0);

        assertThat(member.getName()).isEqualTo(name);
        assertThat(member.getPlatform()).isEqualTo(platform);
        assertThat(member.getPicture()).isEqualTo(picture);
        assertThat(member.getEmail()).isEqualTo(email);
    }

   @Test
    public void member_삭제() throws Exception {
        //given
        String name = "qkrtprjs";
        String email = "qkrtprjs456";
        String platform = "naver";
        String picture = "picture";

        Member member = Member.builder()
                .role(Role.USER)
                .platform(platform)
                .name(name)
                .email(email)
                .picture(picture)
                .build();
        memberRepository.save(member);
        Long id = 1L;
        String url = "http://localhost:" + port + "/api/member/"+id;
        System.out.println(url);
        //when
        mvc.perform(delete(url))
                .andExpect(status().isOk())
                .andDo(print());
        //then
        List<Member> all = memberRepository.findAll();
        assertThat(all.size()).isEqualTo(0);
    }
}


삭제 기능 테스트 정상

  • 삭제 버튼 클리시에 작동하는 js 설계(ajax 사용)
let main = {
    init: function () {
        let _this = this;
        // 버튼을 클릭하는순간 this는 버튼 그 자체로 변하기때문에 변하기전에 this를 저장시켜놓음으로써 delete()함수를 사용할 수 있게 한다
        $('#delete-btn').on('click', function () {
            _this.delete();
        });
    },
    delete: function () {
        let id = $('#loginMemberId').val();
        if (confirm("회원을 삭제하시겠습니까?")) {
            $.ajax({
                url: '/api/member/' + id,
                method: 'DELETE',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8'
            }).done(function () {
                alert("회원이 삭제되었습니다!");
                location.href = '/logout';
            }).fail(function (error) {
                alert(JSON.stringify(error));
            });

        } else {
            alert("취소하였습니다!");
        }
    }
}
main.init();


성공적으로 삭제되었음을 확인할 수 있다!

profile
멋있는 사람 - 일단 하자

0개의 댓글