[내일배움캠프 Spring 4기] 38일차 TIL - 투두앱 백엔드 서버 만들기

서예진·2024년 2월 5일
0

오늘의 학습 키워드 📕

▸ 코드카타
▸ 투두앱 백엔드 서버 만들기


💡투두앱 백엔드 서버 만들기

▼ ERD

  • Entity
    회원 entity
    할일카드 entity
    댓글 entity
  • 관계
    - 회원-할일카드 → 1:N
    - 할일카드-댓글 → 1:N
    - 회원-댓글 → 1:N
    - 댓글 entity는 회원 entity의 기본키와 할일카드 entity의 기본키를 외래키로 가지고 있음

▼ 프로젝트 설정

1. 프로젝트 생성시 dependency 추가

2. JWT, JSON을 build.gradle에 추가

// JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

// json
implementation 'org.json:json:20230227'
  • Client 쪽 코드가 있는 경우는 security 주석 처리

3. 데이터베이스 생성 및 연동

  • src > main > resources > application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/todo
spring.datasource.username=root
spring.datasource.password={비밀번호}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.hibernate.ddl-auto=update

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true

jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
  • 터미널 → create database todo; show databases;
  • Intellij Database 연동: User - root / Password - 내 비밀 번호 / Database - todo / test Connaecction 하고 ok

▼ 회원가입 API 구현

  • username, password를 Client에서 전달받기
  • username은 최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)로 구성되어야 한다.
  • password는 최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9)로 구성되어야 한다.
  • DB에 중복된 username이 없다면 회원을 저장하고 Client 로 성공했다는 메시지, 상태코드 반환하기

- API 명세서

- entity > User / UserRoleEnum

  • entity > User
package com.sparta.mytodo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

    public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
}
  • entity > UserRoleEnum
package com.sparta.mytodo.entity;

public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

- dto > SignupRequestDto

package com.sparta.mytodo.dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class SignupRequestDto {
    private String username;
    private String password;
    private String email;
    private boolean admin = false;
    private String adminToken = "";
}

- controller > UserController

  • 회원 기능을 제어하는 UserController
  • RestEntity를 클라이언트에 반환해야하기 때문에 해당 내용을 처리할 수 있는 메서드가 controller에 있어야 함
package com.sparta.mytodo.controller;

import com.sparta.mytodo.dto.SignupRequestDto;
import com.sparta.mytodo.resposeentity.Message;
import com.sparta.mytodo.resposeentity.StatusEnum;
import com.sparta.mytodo.service.UserService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.nio.charset.Charset;

@Controller
@RequestMapping("/api")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/user/login-page")
    public String loginPage() {
        return "login";
    }

    @GetMapping("/user/signup")
    public String signupPage() {
        return "signup";
    }

    @PostMapping("/user/signup")
    @ResponseBody
    public ResponseEntity<Message> signup(SignupRequestDto requestDto){
        userService.signup(requestDto);

        Message message = new Message();
        HttpHeaders headers= new HttpHeaders();
        headers.setContentType(new MediaType("application", "json", Charset.forName("UTF-8")));
        message.setStatus(StatusEnum.OK);
        message.setMessage("회원가입 성공");

        return ResponseEntity.ok().headers(headers).body(message);
    }
}

참고

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html

- service > UserService

❗️오류

  • 빨간 줄 → Could not autowire.
  • 다른 블로그를 참고해도 해결이 되지 않아
  • config > PasswordConfig 생성
package com.sparta.mytodo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • service > UserService
package com.sparta.mytodo.service;

import com.sparta.mytodo.dto.SignupRequestDto;
import com.sparta.mytodo.entity.User;
import com.sparta.mytodo.entity.UserRoleEnum;
import com.sparta.mytodo.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class UserService {

    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;

    public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
        this.userRepository = userRepository;
        this.passwordEncoder = passwordEncoder;
    }

    // ADMIN_TOKEN
    private final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

    public void signup(SignupRequestDto requestDto) {
        String username = requestDto.getUsername();
        String password = passwordEncoder.encode(requestDto.getPassword());

        // 회원 중복 확인
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
        }

        // 사용자 ROLE 확인
        UserRoleEnum role = UserRoleEnum.USER;
        if (requestDto.isAdmin()) {
            if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
                throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
            }
            role = UserRoleEnum.ADMIN;
        }

        // 사용자 등록
        User user = new User(username, password, role);
        userRepository.save(user);
    }
}

- repository > UserRepository

package com.sparta.mytodo.repository;

import com.sparta.mytodo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

❗️[http://localhost:8080/login] 페이지가 계속 뜸

  • 해결: MytodoApplication
package com.sparta.mytodo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

❗️@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class MytodoApplication {

	public static void main(String[] args) {
		SpringApplication.run(MytodoApplication.class, args);
	}

}
profile
안녕하세요

0개의 댓글