2023.11.11 ~ Present

TETO

Team. Apocalypse

Summary

Back-End Development

2인 팀 프로젝트 / 일정, 루틴 관리 플랫폼

하루의 루틴, 일정, 그리고 할 일을 효과적으로 관리할 수 있는 웹 앱입니다.

평소에는 칸반보드와 캘린더를 각각 다른 플랫폼에서 사용하다가 혼잡한 느낌이 들었고,

특히 하루의 루틴을 효율적으로 관리할 수 있는 플랫폼이 필요하다는 생각에 이를 통합한 아이디어를 기획했습니다.

이전 프로젝트에서 얻은 경험을 활용하여 현재 프로젝트에서는 혼자서 Back-end 개발을 수행하고 있습니다.

Spring Security를 활용한 로그인, 테스트 코드, Spring Rest Docs 등과 같은 다양한 기술들을 혼자서 개발하면서

하나의 애플리케이션을 구축하는 과정에서 필요한 모든 개발 능력을 경험하고 학습하고 있습니다.

Architecture

ERD 바로가기

Experience

혼자 백엔드 개발을 총괄하고 있어서 시간은 오래 걸리고 어렵긴 하지만,

Security, Rest Docs, CRUD, 그리고 DB 관리 등 다양한 기술들을 활용하며 많은 것을 배울 수 있어 상당히 즐거운 프로젝트입니다.

먼저 멤버 간의 로그인과 회원가입 기능을 구현하면서, 회원가입 시 이메일 인증 기능을 처음으로 도입했습니다.

이 기능은 사용자가 입력한 이메일이 실제로 존재하고, 해당 이메일이 사용자 본인 소유인지 확인하는 역할을 수행합니다.

이메일 인증 기능 도입에는 다양한 자료를 참고하며 공부하는 과정이 포함되어 있었는데, 이는 새로운 기술에 대한 학습과 적용에 있어서 매우 흥미로웠습니다.

사용자의 이메일을 통한 인증은 보안적인 측면에서 중요하며, 이를 구현함으로써 사용자 계정의 안전성을 높일 수 있습니다.

서버는 Google SMTP를 활용함으로써 안정적이고 신속한 이메일 전송을 구현할 수 있었습니다.

이를 통해 사용자가 회원가입 시 입력한 이메일 주소로 안전하게 확인 링크를 전송할 수 있었습니다.

소스코드 보기

            
              @Controller
              @RequiredArgsConstructor
              public class MailController {
                  private final MailService mailService;

                  @ResponseBody
                  @PostMapping("/signup/mail")
                  public String MailSend(String mail) {
                      int number = mailService.sendMail(mail);
                      String num = "" + number;
                      return num;
                  }
              }

              @Service
              @RequiredArgsConstructor
              public class MailService {
                  @Autowired
                  private final JavaMailSender javaMailSender;
                  private static final String senderEmail = "preasin.kr@gmail.com";
                  private static int number;

                  public static void createNumber() {
                      number = (int)(Math.random() * (90000)) + 100000;
                  }

                  public MimeMessage CreateMail(String mail) {
                      createNumber();
                      MimeMessage message = javaMailSender.createMimeMessage();

                      try {
                          message.setFrom(senderEmail);
                          message.setRecipients(MimeMessage.RecipientType.TO, mail);
                          message.setSubject("이메일 인증");
                          String body = "";
                          body += "

" + "요청하신 인증 번호입니다." + "

"; body += "

" + number + "

"; body += "

" + "감사합니다." + "

"; message.setText(body, "UTF-8", "html"); } catch (MessagingException e) { e.printStackTrace(); } return message; } public int sendMail(String mail) { MimeMessage message = CreateMail(mail); javaMailSender.send(message); return number; } }

이메일 인증을 통한 회원가입 후, 계정에 자동으로 생성되는 멤버 코드 기능도 추가하였습니다.

이 멤버 코드는 중복 닉네임을 허용하기 위한 대체 수단으로 활용되며,

랜덤한 알파벳 4글자와 memberId의 조합으로 자동 생성되어 회원가입 시 자동으로 할당됩니다.

이를 통해 사용자들은 서로 같은 닉네임을 가질 수 있으면서도 고유한 식별 코드를 갖게 됩니다.

멤버 코드는 캘린더 페이지에서 공유 캘린더 기능을 이용하기 위해 활용됩니다.

이 기능은 다른 사용자와 캘린더 일정을 공유할 수 있게 해주며, 추후에는 친구 추가 기능에도 사용될 예정입니다.

소스코드 보기

            
              private String createMemberCode(String memberId) {
                  String[] alphabet = 
                  new String[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
                  
                  Random random = new Random();
                  StringBuilder randomCode = new StringBuilder();
          
                  for (int i=0; i<4; i++) {
                      int randomIdx = random.nextInt(alphabet.length);
                      randomCode.append(alphabet[randomIdx]);
                  }
          
                  String memberCode = randomCode.toString() + memberId;
                  return memberCode;
              }
            
          

또한 프론트엔드 개발자가 빠르고 쉽게 이해하고 활용할 수 있도록 Spring Rest Docs를 도입했습니다.

이론적으로는 이미 알고 있었던 내용이었지만,

실제로 구현하고 적용하는 과정에서 수많은 오류가 발생하여 이를 해결하는 데 상당한 어려움을 겪었습니다.

          
            expected:<MEMBER_DELETE> but was:<MEMBER_DELETE>
          
        

특히 테스트 코드에서 post할때 timeStamp, Member.Status 자료형이 get과 같지 않아

위와 같은 오류가 발생해 임의로 데이터를 만들어 해결했습니다.

소스코드 보기

            
              Timestamp time = Timestamp.valueOf("2023-12-13 10:30:00");
              MemberDto.ResponseDto response = new MemberDto.ResponseDto(
                      1L,
                      "Demuu",
                      "demuu@example.com",
                      "test1234",
                      "null",
                      "null",
                      Member.Status.MEMBER_ACTIVE,
                      time,
                      time
              );
              
              public static List getMultiResponseBody() {
                      Timestamp time = Timestamp.valueOf("2023-12-13 10:30:00");
                      return List.of(
                              new MemberDto.ResponseDto(
                                      1L,
                                      "Demuu1",
                                      "demuu1@example.com",
                                      "test1111",
                                      "test1",
                                      "null",
                                      Member.Status.MEMBER_ACTIVE,
                                      time,
                                      time
                              ),
                              new MemberDto.ResponseDto(
                                      2L,
                                      "Demuu2",
                                      "demuu2@example.com",
                                      "test2222",
                                      "test2",
                                      "null",
                                      Member.Status.MEMBER_ACTIVE,
                                      time,
                                      time
                              )
                      );
                  }
            
          

테스트 코드에 Rest Docs를 적용하니 코드의 가독성이 감소하고 코드의 길이가 늘어나는 등의 문제가 발생했습니다.

대규모 프로젝트에서는 효과적일 수 있지만,

현재 프로젝트에서는 수기로 문서를 작성하는 것과 시간적인 측면에서 큰 차이가 없어 오히려 효율적이지 않을 것으로 판단했습니다.

소스코드 보기

            
              @Test
              void postMemberTest() throws Exception {
                  MemberDto.PostDto post = new MemberDto.PostDto("Demuu","demuu@example.com","testpassword");
                  String content = gson.toJson(post);
          
                  given(mapper.postToMember(Mockito.any(MemberDto.PostDto.class))).willReturn(new Member());
          
                  Member mockResultMember = new Member();
                  mockResultMember.setMemberId(1L);
                  given(memberService.createMember(Mockito.any(Member.class))).willReturn(mockResultMember);
          
                  ResultActions actions = mockMvc.perform(
                          post("/members/signup")
                                  .accept(MediaType.APPLICATION_JSON)
                                  .contentType(MediaType.APPLICATION_JSON)
                                  .content(content)
                  );
          
                  actions.andExpect(status().isCreated())
                          .andExpect(header().string("Location", is(startsWith("/members/1"))))
                          .andDo(document(
                                  "post-member",
                                  getRequestPreProcessor(),
                                  getResponsePreProcessor(),
                                  requestFields(
                                          List.of(
                                                  fieldWithPath("name").type(JsonFieldType.STRING).description("이름"),
                                                  fieldWithPath("email").type(JsonFieldType.STRING).description("이메일"),
                                                  fieldWithPath("password").type(JsonFieldType.STRING).description("비밀번호")
                                          )
                                  ),
                                  responseHeaders(
                                          headerWithName(HttpHeaders.LOCATION).description("Location header. 등록된 리소스의 URI")
                                  )
                          ));
              }

              ...
              
              ...
            
          

UNCOVER 프로젝트에서는 Spring을 능숙하게 활용하는 데 어려움을 겪었지만,

현재 프로젝트에서는 대부분의 기능을 능숙하게 구현하고 새로운 기술을 탐험하며 지속적으로 학습할 수 있어서 기쁘게 생각합니다.

앞으로도 프로젝트를 완성하고, 서비스를 제공하며 유지보수를 진행하고자 합니다.