자, React에서
client.post('/api/auth/register',{username,password});
이렇게 전송한 API를 스프링에서 어떻게 받을 것인가.
package com.example.SpringAndReact.Domain;
public class Member {
private Long id;
private String userId;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
회원 레포지를 만들기에 앞서서 먼저 인터페이스를 만들자.
OCP(개방 폐쇄), DIP(의존 관계 역전) 의 SOLID 원칙을 지키기 위해 노력해봅시다.
package com.example.SpringAndReact.Repository;
import com.example.SpringAndReact.Domain.Member;
public interface MemberRepository {
Member saveMember(Member member);
Member findByUserId(Long id);
}
package com.example.SpringAndReact.Repository;
import com.example.SpringAndReact.Domain.Member;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class MemoryMemberRepository implements MemberRepository {
private static Map<Long,Member> store = new HashMap<>();
private static long sequence = 0L;
@Override
public Member saveMember(Member member) {
member.setId(++sequence);
store.put(member.getId(), member);
return member;
}
@Override
public Optional<Member> findByUserId(String id) {
return store.values().stream().filter(member->member.getUserId().equals(id)).findAny();
}
}
package com.example.SpringAndReact.Service;
import com.example.SpringAndReact.Domain.Member;
public interface MemberService {
void join(Member member);
}
아무래도 회원 서비스 클래스에서 로직을 짜려면 레포지토리를 건드려야 하는데,
private final MemberRepository memberRepository = new MemoryMemberRepository();
이렇게 짜버리면 SOLID 원칙의 OCP, DIP문제 두가지 모두 생길 수 있다. 따라서 AppConfig로 이를 해결해보자!
먼저 구현체에서 생성자 주입(DI) 방식을 활용하고, 주입은 AppConfig에게 맡긴다
package com.example.SpringAndReact.Service;
import com.example.SpringAndReact.Domain.Member;
import com.example.SpringAndReact.Repository.MemberRepository;
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
validateDuplicateMember(member);
memberRepository.saveMember(member);
}
@Override
public void validateDuplicateMember(Member member) {
memberRepository.findByUserId(member.getUserId()).ifPresent(mem->{
try{
throw new IllegalAccessException("이미 존재하는 회원입니다.")
}catch(IllegalAccessException e){
throw new RuntimeException(e);
}
});
}
}
package com.example.SpringAndReact;
import com.example.SpringAndReact.Repository.MemoryMemberRepository;
import com.example.SpringAndReact.Service.MemberService;
import com.example.SpringAndReact.Service.MemberServiceImpl;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
public MemberService memberService (){
return new MemberServiceImpl(new MemoryMemberRepository());
}
}
먼저 post로 받아온 정보를 받아와서 Member 도메인 객체로 지정하려면 API부터 처리한다.
해당 API 처리과정에서 쓸 MemberForm 클래스를 먼저 정의하자
package com.example.SpringAndReact.controller;
public class MemberForm {
private String UserId;
private String password;
public String getUserId() {
return UserId;
}
public void setUserId(String userId) {
UserId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
그 후에,
form으로 들어온 정보를 @postmapping함수 내에서 다음과 같이 처리해보겠다.
마찬가지로 DI 를 이용한다.
RequestMapping으로 "api/auth" path를 먼저 받고,
그 후에 Request 별 Mapping을 해당 path에 추가시키는 형태로 받는다
package com.example.SpringAndReact.controller;
import com.example.SpringAndReact.Domain.Member;
import com.example.SpringAndReact.Service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("api/auth")
public class MemberController {
private final MemberService memberService;
@Autowired
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@PostMapping("/register")
@ResponseBody
public Member createForm(@RequestBody MemberForm form){
Member member = new Member();
member.setUsername(form.getUsername());
member.setPassword(form.getPassword());
System.out.println(member.getUsername());
System.out.println(member.getPassword());
memberService.join(member);
return member;
}
};
후에, MemberService 관련 생성자 주입 과정을 AppConfig에 추가해주자.
하지만 이 코드는 동작되지 않는다!!!!
이미 Controller에서 memberService롤 autowired해주었기 때문이다. = 사실상 이미 Bean에 올라간 것과 같은 논리 (Controller 에서의 autowired는 필수불가결하다)
따라서 config에서 또다시 정의할 생각을 하지 말자.
package com.example.SpringAndReact;
import com.example.SpringAndReact.Repository.MemoryMemberRepository;
import com.example.SpringAndReact.Service.MemberService;
import com.example.SpringAndReact.Service.MemberServiceImpl;
import com.example.SpringAndReact.controller.MemberController;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean
public MemberService memberService (){
return new MemberServiceImpl(new MemoryMemberRepository());
}
@Bean
public MemberController memberController() {
return new MemberController(memberService());}
}
추가로, 리액트에서 프록시 설정을 바꾸면 무조건 리액트 서버를 열고 꺼야합니다.
즉, 페이지 렌더링하는것은 바로 새로고침되지만, 서버 설정은 서버를 열고 꺼야함!!
(프록시 설정을 https://localhost:8080으로 해서 안되었다가, https를 http로 바꾸면서 일어난 일)
@PostMapping("/register")
public String createForm(@RequestBody MemberForm form){
Member member = new Member();
System.out.println(form.getUserId());
// member.setUserId(form.getUserId());
// member.setPassword(form.getPassword());
// memberService.join(member);
return "login";
}
Controller에서 넘어온 data 의 form 을 확인해보았지만 null 이 찍힘 매우 슬프다..
그래서 먼저 axios 의 post가 잘되는지 체크해보았다.
export const register = ({username,password}) =>
client.post('/api/auth/register',{username,password}).then((response)=>
{console.log(response.data);}).catch((error)=>{console.log(error);});
왜 또 회원가입조차안되는거지..
그냥 원상태로 되돌렸다.. 는 무슨
해당 문제는 클라이언트에서 서버로 전송하는 form 태그의 post의 '이름'이 달라서 생긴 문제다.
API를 전송하는 lib/api 파일에
//회원 가입
export const register = ({username,password}) =>
client.post('/api/auth/register',{username,password});
이렇게 적혀있다는 것은, username과 password를 전송하겠다는 뜻이다.
따라서, 서버에서 받을 때에도 해당 이름과 맞추어서 username과 password로 저장해야한다.
이 전에 문제에서는 password는 같아서 password는 제대로 받아졌지만, 내가 디버그를 password를 하지 않고, userId로 디버그를 해서 아예 API가 받아와지지 않는 줄 알았다. 하지만 그것은 아니었다! 그러면 우리는 백엔드 Controller에서 받아올 때,
@PostMapping("/register")
public String createForm(@RequestBody MemberForm form){
Member member = new Member();
member.setUsername(form.getUsername());
member.setPassword(form.getPassword());
System.out.println(member.getUsername());
System.out.println(member.getPassword());
memberService.join(member);
return "login";
}
이렇게 받는데, 해당 MemberForm 클래스가 정의되기를
package com.example.SpringAndReact.controller;
public class MemberForm {
private String UserId;
private String password;
public String getUserId() {
return UserId;
}
public void setUserId(String userId) {
UserId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
이렇게 정의가 되어있으니 이름이 달라서 userId는 받아와질 턱이 없었다.
따라서 다음처럼 바꾸고, 해당 파일을 불러오는 모든 코드를 수정해서 체크를 해보면
package com.example.SpringAndReact.controller;
public class MemberForm {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
로그인 성공 콘솔 창
Controller에서 받아온 아이디 비밀번호
중복 가입 구현 체크
잘 보고 갑니다!