추가적으로 로그인한 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 {
}
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" 로 저장된 객체가 저장되게 됩니다!
이전에 form형태를 갖고 회원 삭제를 진행했다면 이번에는 js를 통한 ajax로 회원삭제를 진행해보자. 추가로 수정 작업도 진행하려 했지만 OAuth2.0을 사용한 로그인과 회원가입만 가능하기때문에 수정은 불가하고 삭제만 가능하도록 설정
@DeleteMapping("/api/member/{id}")
private Long delete(@PathVariable Long id) {
memberService.delete(id);
return id;
}
@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);
}
}
삭제 기능 테스트 정상
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();
성공적으로 삭제되었음을 확인할 수 있다!