diff --git a/src/main/java/com/book/backend/domain/auth/controller/AuthController.java b/src/main/java/com/book/backend/domain/auth/controller/AuthController.java index a0b152cd..02a56949 100644 --- a/src/main/java/com/book/backend/domain/auth/controller/AuthController.java +++ b/src/main/java/com/book/backend/domain/auth/controller/AuthController.java @@ -1,15 +1,11 @@ package com.book.backend.domain.auth.controller; import com.book.backend.domain.auth.dto.JwtTokenDto; -import com.book.backend.domain.auth.dto.LoginDto; import com.book.backend.domain.auth.dto.LoginSuccessResponseDto; -import com.book.backend.domain.auth.dto.SignupDto; -import com.book.backend.domain.auth.service.AppleService; import com.book.backend.domain.auth.service.AuthService; -import com.book.backend.domain.auth.service.KakaoService; -import com.book.backend.domain.user.dto.UserDto; +import com.book.backend.domain.auth.service.OAuthService; +import com.book.backend.domain.oidc.Provider; import com.book.backend.global.ResponseTemplate; -import com.book.backend.global.log.RequestLogger; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -17,49 +13,22 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/auth") @RequiredArgsConstructor @Slf4j -@Tag(name = "유저 관리", description = "회원 탈퇴 / 회원가입 / JWT 토큰 재발급 / 로그아웃 / 로그인 / 카카오 로그인 / 애플 로그인") +@Tag(name = "유저 관리", description = "회원 탈퇴 / JWT 토큰 재발급 / 로그아웃 / 카카오 로그인 / 애플 로그인") public class AuthController { - private final AuthService authService; - private final KakaoService kakaoService; - private final AppleService appleService; + private final OAuthService OAuthService; private final ResponseTemplate responseTemplate; - @Operation(summary = "회원가입", description = "기본 회원가입을 진행합니다.", - responses = {@ApiResponse(responseCode = "201", content = @Content(schema = @Schema(implementation = UserDto.class)), - description = UserDto.description)}) - @PostMapping("/signup") - public ResponseEntity signup(@Valid @RequestBody SignupDto signupDto) { - RequestLogger.body(signupDto); - - UserDto userDto = authService.signup(signupDto); - return responseTemplate.success(userDto, HttpStatus.CREATED); - } - - @Operation(summary = "로그인", description = "기본 로그인을 진행합니다.", - responses = {@ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = LoginSuccessResponseDto.class)), - description = LoginSuccessResponseDto.description)}) - @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginDto loginDto) { - RequestLogger.body(loginDto); - - LoginSuccessResponseDto loginSuccessResponseDto = authService.login(loginDto); - return responseTemplate.success(loginSuccessResponseDto, HttpStatus.OK); - } - @Operation(summary = "로그아웃", description = "로그아웃을 진행합니다.", responses = {@ApiResponse(responseCode = "200", description = "로그아웃이 완료되었습니다.")}) @PostMapping("/logout") @@ -86,7 +55,7 @@ public ResponseEntity deleteAccount(HttpServletRequest request) { description = LoginSuccessResponseDto.description)}) @PostMapping("/kakaoLogin") public ResponseEntity kakaoLogin(@RequestParam String idToken) { - LoginSuccessResponseDto loginSuccessResponseDto = kakaoService.kakaoLogin(idToken); + LoginSuccessResponseDto loginSuccessResponseDto = OAuthService.oAuthLogin(Provider.KAKAO, idToken); return responseTemplate.success(loginSuccessResponseDto, HttpStatus.OK); } @@ -99,7 +68,7 @@ public ResponseEntity kakaoLogin(@RequestParam String idToken) { description = LoginSuccessResponseDto.description)}) @PostMapping("/appleLogin") public ResponseEntity appleLogin(@RequestParam String idToken) { - LoginSuccessResponseDto loginSuccessResponseDto = appleService.appleLogin(idToken); + LoginSuccessResponseDto loginSuccessResponseDto = OAuthService.oAuthLogin(Provider.APPLE, idToken); return responseTemplate.success(loginSuccessResponseDto, HttpStatus.OK); } diff --git a/src/main/java/com/book/backend/domain/auth/dto/KakaoTokenResponseDto.java b/src/main/java/com/book/backend/domain/auth/dto/KakaoTokenResponseDto.java deleted file mode 100644 index 2f032dcd..00000000 --- a/src/main/java/com/book/backend/domain/auth/dto/KakaoTokenResponseDto.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.book.backend.domain.auth.dto; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class KakaoTokenResponseDto { - @JsonProperty("access_token") - private String accessToken; - - // 아래의 프로퍼티들은 필요시 추가 -// @JsonProperty("token_type") -// private String tokenType; -// -// @JsonProperty("refresh_token") -// private String refreshToken; -// - @JsonProperty("id_token") - private String idToken; -// -// @JsonProperty("expires_in") -// private String expiresIn; -// -// @JsonProperty("scope") -// private String scope; -// -// @JsonProperty("refresh_token_expires_in") -// private String refreshTokenExpiresIn; - -} diff --git a/src/main/java/com/book/backend/domain/auth/dto/KakaoUserInfoDto.java b/src/main/java/com/book/backend/domain/auth/dto/KakaoUserInfoDto.java deleted file mode 100644 index 10d56f63..00000000 --- a/src/main/java/com/book/backend/domain/auth/dto/KakaoUserInfoDto.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.book.backend.domain.auth.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class KakaoUserInfoDto { - private Long id; -} diff --git a/src/main/java/com/book/backend/domain/auth/dto/LoginDto.java b/src/main/java/com/book/backend/domain/auth/dto/LoginDto.java deleted file mode 100644 index 3970c662..00000000 --- a/src/main/java/com/book/backend/domain/auth/dto/LoginDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.book.backend.domain.auth.dto; - -import jakarta.validation.constraints.NotBlank; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -public class LoginDto { - @NotBlank(message = "아이디는 필수 입력값입니다.") - private String loginId; - - @NotBlank(message = "비밀번호는 필수 입력값입니다.") - private String password; -} \ No newline at end of file diff --git a/src/main/java/com/book/backend/domain/auth/dto/SignupDto.java b/src/main/java/com/book/backend/domain/auth/dto/SignupDto.java deleted file mode 100644 index 3f3a34e1..00000000 --- a/src/main/java/com/book/backend/domain/auth/dto/SignupDto.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.book.backend.domain.auth.dto; - -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Past; -import jakarta.validation.constraints.Pattern; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import java.time.LocalDate; - -@Getter -@Setter -@NoArgsConstructor -public class SignupDto { - @NotBlank(message = "아이디는 필수 입력값입니다.") - private String loginId; - - @NotBlank(message = "닉네임은 필수 입력값입니다.") - private String nickname; - - @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", - message = "비밀번호는 숫자, 영문자, 특수문자를 포함한 8~16자리여야 합니다.") - private String password; - - @NotBlank(message = "성별은 필수 입력값입니다.") - @Pattern(regexp = "NOT_SELECTED|MAN|WOMAN", message = "성별은 NOT_SELECTED, MAN, WOMAN 중 하나여야 합니다.") - private String gender; - - @Past(message = "현재 날짜보다 이전 날짜여야 합니다.") - private LocalDate birthDate; - -} diff --git a/src/main/java/com/book/backend/domain/auth/mapper/AuthMapper.java b/src/main/java/com/book/backend/domain/auth/mapper/AuthMapper.java deleted file mode 100644 index bf784207..00000000 --- a/src/main/java/com/book/backend/domain/auth/mapper/AuthMapper.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.book.backend.domain.auth.mapper; - -import com.book.backend.domain.auth.dto.SignupDto; -import com.book.backend.domain.user.entity.Gender; -import com.book.backend.domain.user.entity.User; -import com.book.backend.domain.user.mapper.UserMapper; -import lombok.RequiredArgsConstructor; -import org.modelmapper.ModelMapper; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -@Component -@RequiredArgsConstructor -public class AuthMapper { - - private final ModelMapper mapper; - private final UserMapper userMapper; - private final PasswordEncoder passwordEncoder; - - public User convertToUser(SignupDto signupDto) { - User user = mapper.map(signupDto, User.class); - user.setGender(userMapper.convertStringToGender(signupDto.getGender())); - user.setPassword(passwordEncoder.encode(signupDto.getPassword())); - return user; - } - -} diff --git a/src/main/java/com/book/backend/domain/auth/service/AuthService.java b/src/main/java/com/book/backend/domain/auth/service/AuthService.java index 86856ba1..9013b6c7 100644 --- a/src/main/java/com/book/backend/domain/auth/service/AuthService.java +++ b/src/main/java/com/book/backend/domain/auth/service/AuthService.java @@ -1,13 +1,7 @@ package com.book.backend.domain.auth.service; import com.book.backend.domain.auth.dto.JwtTokenDto; -import com.book.backend.domain.auth.dto.LoginDto; -import com.book.backend.domain.auth.dto.LoginSuccessResponseDto; -import com.book.backend.domain.auth.dto.SignupDto; -import com.book.backend.domain.auth.mapper.AuthMapper; -import com.book.backend.domain.user.dto.UserDto; import com.book.backend.domain.user.entity.User; -import com.book.backend.domain.user.mapper.UserMapper; import com.book.backend.domain.user.repository.UserRepository; import com.book.backend.domain.user.service.UserService; import com.book.backend.exception.CustomException; @@ -19,17 +13,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -37,61 +27,10 @@ public class AuthService { private final UserRepository userRepository; private final UserService userService; - private final AuthMapper authMapper; - private final UserMapper userMapper; - private final AuthenticationManager authenticationManager; private final CustomUserDetailsService userDetailsService; private final JwtUtil jwtUtil; private final RedisTemplate redisTemplate; - @Transactional - public UserDto signup(SignupDto signupDto) { - log.trace("AuthService > signup()"); - - userService.validateNotDuplicatedUsername(signupDto.getLoginId()); - userService.validateNotDuplicatedNickname(signupDto.getNickname()); - - User user = authMapper.convertToUser(signupDto); - user.setRegDate(LocalDateTime.now()); - - User savedUser = userRepository.save(user); - - return userMapper.convertToUserDto(savedUser); - } - - @Transactional - public LoginSuccessResponseDto login(LoginDto loginDto) { - log.trace("AuthService > login()"); - - Authentication authentication; - try { - // 사용자 인증 시도 - authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(loginDto.getLoginId(), loginDto.getPassword())); - - // 인증 성공 시 Security Context에 인증 정보 저장 - SecurityContextHolder.getContext().setAuthentication(authentication); - } catch (AuthenticationException e) { - throw new CustomException(ErrorCode.INVALID_CREDENTIALS); - } - - // 인증 성공 후 유저 정보 로드 - UserDetails userDetails = userDetailsService.loadUserByUsername(loginDto.getLoginId()); - JwtTokenDto jwtTokenDto = jwtUtil.generateToken(userDetails); - - User user = userRepository.findByLoginId(loginDto.getLoginId()) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); - - // Redis에 RefreshToken 저장 - jwtUtil.storeRefreshTokenInRedis(authentication, jwtTokenDto.getRefreshToken()); - - return LoginSuccessResponseDto.builder() - .userId(user.getUserId()) - .accessToken(jwtTokenDto.getAccessToken()) - .refreshToken(jwtTokenDto.getRefreshToken()) - .build(); - } - public void logout(HttpServletRequest request) { log.trace("AuthService > logout()"); diff --git a/src/main/java/com/book/backend/domain/auth/service/CustomUserDetailsService.java b/src/main/java/com/book/backend/domain/auth/service/CustomUserDetailsService.java index 9f459c0c..fb25ceaf 100644 --- a/src/main/java/com/book/backend/domain/auth/service/CustomUserDetailsService.java +++ b/src/main/java/com/book/backend/domain/auth/service/CustomUserDetailsService.java @@ -20,17 +20,15 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - // username이 아닌 loginId로 간주 log.trace("CustomUserDetailsService > loadUserByUsername()"); - User user = userRepository.findByLoginId(username) - .orElseGet(() -> userRepository.findByKakaoId(username) - .orElseGet(() -> userRepository.findByAppleId(username) - .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)))); + userRepository.findByKakaoId(username) + .orElseGet(() -> userRepository.findByAppleId(username) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND))); return org.springframework.security.core.userdetails.User.builder() .username(username) - .password(user.getPassword()) + .password("unused") .authorities("ROLE_USER") .build(); } diff --git a/src/main/java/com/book/backend/domain/auth/service/KakaoService.java b/src/main/java/com/book/backend/domain/auth/service/KakaoService.java deleted file mode 100644 index 071870ae..00000000 --- a/src/main/java/com/book/backend/domain/auth/service/KakaoService.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.book.backend.domain.auth.service; - -import com.book.backend.domain.auth.dto.JwtTokenDto; -import com.book.backend.domain.auth.dto.KakaoTokenResponseDto; -import com.book.backend.domain.auth.dto.KakaoUserInfoDto; -import com.book.backend.domain.auth.dto.LoginSuccessResponseDto; -import com.book.backend.domain.oidc.OidcProviderFactory; -import com.book.backend.domain.oidc.Provider; -import com.book.backend.domain.user.entity.Gender; -import com.book.backend.domain.user.entity.User; -import com.book.backend.domain.user.repository.UserRepository; -import com.book.backend.domain.user.service.UserService; -import com.book.backend.exception.CustomException; -import com.book.backend.exception.ErrorCode; -import com.book.backend.util.JwtUtil; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.*; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestTemplate; - -import java.time.LocalDateTime; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Slf4j -public class KakaoService { - @Value("${kakao.restapiKey}") - private String restApiKey; - @Value("${kakao.redirectUri}") - private String redirectUri; - @Value("${kakao.tokenRequestUri}") - private String tokenRequestUri; - @Value("${kakao.userInfoUri}") - private String userInfoUri; - - private final RestTemplate restTemplate; - private final UserRepository userRepository; - private final UserService userService; - private final CustomUserDetailsService userDetailsService; - private final JwtUtil jwtUtil; - private final OidcProviderFactory oidcProviderFactory; - - // Redirect URI에 전달된 코드값으로 Access Token 요청 - public KakaoTokenResponseDto getAccessToken(String authorizationCode) { - log.trace("KakaoService > getAccessToken()"); - - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - - MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("grant_type", "authorization_code"); - body.add("client_id", restApiKey); - body.add("redirect_uri", redirectUri); - body.add("code", authorizationCode); - - HttpEntity> request = new HttpEntity<>(body, headers); - - return restTemplate.postForEntity(tokenRequestUri, request, KakaoTokenResponseDto.class).getBody(); - } catch (HttpClientErrorException e) { - log.trace("HttpClientErrorException: Status code: {}, Response body: {}", e.getStatusCode(), e.getResponseBodyAsString()); - throw new CustomException(ErrorCode.BAD_REQUEST); - } catch (HttpServerErrorException e) { - throw new CustomException(ErrorCode.KAKAO_SERVER_ERROR); - } - } - - // Access Token으로 유저 정보 요청 - public KakaoUserInfoDto getUserInfo(String accessToken) { - log.trace("KakaoService > getUserInfo()"); - - try { - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - headers.setBearerAuth(accessToken); - - HttpEntity request = new HttpEntity<>(headers); - - // 일단 id만 받아오도록 구현, 향후 필요 시 유저 프로필 정보 가져오도록 구현 가능 - ResponseEntity response = restTemplate.exchange( - userInfoUri, HttpMethod.GET, request, KakaoUserInfoDto.class); - return response.getBody(); - - } catch (HttpClientErrorException e) { - throw new CustomException(ErrorCode.BAD_REQUEST); - } catch (HttpServerErrorException e) { - throw new CustomException(ErrorCode.KAKAO_SERVER_ERROR); - } - } - - // TODO: appleLogin과 중복되는 코드 리팩토링 필요 - // 카카오 로그인 - @Transactional - public LoginSuccessResponseDto kakaoLogin(String idToken) { - log.trace("KakaoService > kakaoLogin()"); - - if (idToken == null || idToken.isEmpty()){ - throw new CustomException(ErrorCode.ID_TOKEN_IS_NULL); - } - - String providerId = oidcProviderFactory.getProviderId(Provider.KAKAO, idToken); - - // kakaoId로 유저 조회 - User user = userService.findByUsername(providerId); - Boolean isNewUser = Boolean.FALSE; - - // 조회된 유저가 없을 시 회원가입 처리 - if (user == null) { - isNewUser = Boolean.TRUE; - User newUser = new User(); - newUser.setKakaoId(providerId); - newUser.setRegDate(LocalDateTime.now()); - newUser.setPassword("unused"); // 카카오 로그인 사용자는 패스워드 사용하지 않음 - newUser.setLoginId(null); // 카카오 로그인 사용자는 loginId가 null - newUser.setNickname(""); // 빈 문자열로 설정 - newUser.setGender(Gender.G0); - user = newUser; - } - - userRepository.save(user); - - // UserDetailsService를 사용하여 UserDetails 객체 생성 - UserDetails userDetails = userDetailsService.loadUserByUsername(providerId); - JwtTokenDto jwtTokenDto = jwtUtil.generateToken(userDetails); - - // 사용자 인증 정보 생성 및 SecurityContext에 저장 - Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(), userDetails.getAuthorities()); - SecurityContextHolder.getContext().setAuthentication(authentication); - - // Redis에 RefreshToken 저장 - jwtUtil.storeRefreshTokenInRedis(authentication, jwtTokenDto.getRefreshToken()); - - return LoginSuccessResponseDto.builder() - .userId(user.getUserId()) - .isNewUser(isNewUser) - .accessToken(jwtTokenDto.getAccessToken()) - .refreshToken(jwtTokenDto.getRefreshToken()) - .build(); - } -} diff --git a/src/main/java/com/book/backend/domain/auth/service/AppleService.java b/src/main/java/com/book/backend/domain/auth/service/OAuthService.java similarity index 76% rename from src/main/java/com/book/backend/domain/auth/service/AppleService.java rename to src/main/java/com/book/backend/domain/auth/service/OAuthService.java index 0ea724af..1930b36a 100644 --- a/src/main/java/com/book/backend/domain/auth/service/AppleService.java +++ b/src/main/java/com/book/backend/domain/auth/service/OAuthService.java @@ -8,6 +8,8 @@ import com.book.backend.domain.user.entity.User; import com.book.backend.domain.user.repository.UserRepository; import com.book.backend.domain.user.service.UserService; +import com.book.backend.exception.CustomException; +import com.book.backend.exception.ErrorCode; import com.book.backend.util.JwtUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,21 +26,25 @@ @RequiredArgsConstructor @Transactional(readOnly = true) @Slf4j -public class AppleService { +public class OAuthService { private final UserRepository userRepository; private final UserService userService; private final CustomUserDetailsService userDetailsService; - private final OidcProviderFactory oidcProviderFactory; private final JwtUtil jwtUtil; + private final OidcProviderFactory oidcProviderFactory; - // TODO: kakaoLogin과 중복되는 코드 리팩토링 필요 + // 카카오 로그인 @Transactional - public LoginSuccessResponseDto appleLogin(String idToken) { - log.trace("AppleService > appleLogin()"); + public LoginSuccessResponseDto oAuthLogin(Provider provider, String idToken) { + log.trace("OAuthService > oAuthLogin()"); + + if (idToken == null || idToken.isEmpty()){ + throw new CustomException(ErrorCode.ID_TOKEN_IS_NULL); + } - String providerId = oidcProviderFactory.getProviderId(Provider.APPLE, idToken); + String providerId = oidcProviderFactory.getProviderId(provider, idToken); - // appleId로 유저 조회 + // kakaoId로 유저 조회 User user = userService.findByUsername(providerId); Boolean isNewUser = Boolean.FALSE; @@ -46,10 +52,12 @@ public LoginSuccessResponseDto appleLogin(String idToken) { if (user == null) { isNewUser = Boolean.TRUE; User newUser = new User(); - newUser.setAppleId(providerId); + if (provider == Provider.KAKAO) { // 카카오 로그인인 경우 + newUser.setKakaoId(providerId); + } else { // 애플 로그인인 경우 + newUser.setAppleId(providerId); + } newUser.setRegDate(LocalDateTime.now()); - newUser.setPassword("unused"); // 애플 로그인 사용자는 패스워드 사용하지 않음 - newUser.setLoginId(null); // 애플 로그인 사용자는 loginId가 null newUser.setNickname(""); // 빈 문자열로 설정 newUser.setGender(Gender.G0); user = newUser; @@ -62,7 +70,7 @@ public LoginSuccessResponseDto appleLogin(String idToken) { JwtTokenDto jwtTokenDto = jwtUtil.generateToken(userDetails); // 사용자 인증 정보 생성 및 SecurityContext에 저장 - Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(), userDetails.getAuthorities()); + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); // Redis에 RefreshToken 저장 diff --git a/src/main/java/com/book/backend/domain/message/mapper/MessageMapper.java b/src/main/java/com/book/backend/domain/message/mapper/MessageMapper.java index 86284ac3..a30752d5 100644 --- a/src/main/java/com/book/backend/domain/message/mapper/MessageMapper.java +++ b/src/main/java/com/book/backend/domain/message/mapper/MessageMapper.java @@ -48,7 +48,7 @@ public Message convertToMessage(MessageRequestDto dto) { public MessageResponseDto convertToMessageResponseDto(Message message) { log.trace("MessageMapper > convertToMessageResponseDto()"); - // FIXME : loginId 가 nullable 해서 중복으로 값을 찾아옴 + // user DB id 로 조회 User user = userRepository.findById(message.getUser().getUserId()).orElseThrow(); diff --git a/src/main/java/com/book/backend/domain/user/dto/UserDto.java b/src/main/java/com/book/backend/domain/user/dto/UserDto.java index a64b6c6e..8544611c 100644 --- a/src/main/java/com/book/backend/domain/user/dto/UserDto.java +++ b/src/main/java/com/book/backend/domain/user/dto/UserDto.java @@ -12,24 +12,20 @@ @AllArgsConstructor public class UserDto { private Long userId; - private String loginId; private String kakaoId; private String appleId; private LocalDateTime regDate; private String nickname; - private String password; private String gender; private LocalDate birthDate; private String profileImageUrl; public static final String description = "userId : 유저 아이디 | " + - "loginId : 유저 로그인 아이디 (일반 로그인 시 생성) | " + "kakaoId : 유저 카카오 아이디 (카카오 로그인 시 생성) | " + "appleId : 유저 애플 아이디 (애플 로그인 시 생성) | " + "regDate : 가입 시각 | " + "nickname : 닉네임 | " + - "password : 암호화된 비밀번호 (일반 로그인 시 생성) | " + "gender : 성별 | " + "birthDate : 생일" + "profileImageUrl : 프로필 이미지 URL (지금은 null)"; diff --git a/src/main/java/com/book/backend/domain/user/entity/User.java b/src/main/java/com/book/backend/domain/user/entity/User.java index cd21d0c0..aa4fe54d 100644 --- a/src/main/java/com/book/backend/domain/user/entity/User.java +++ b/src/main/java/com/book/backend/domain/user/entity/User.java @@ -22,9 +22,6 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; - @Column(unique = true) - private String loginId; - @Column(unique = true) private String kakaoId; @@ -36,8 +33,6 @@ public class User { @Column(unique = true) private String nickname; - private String password; - @Enumerated(EnumType.ORDINAL) private Gender gender; diff --git a/src/main/java/com/book/backend/domain/user/repository/UserRepository.java b/src/main/java/com/book/backend/domain/user/repository/UserRepository.java index 7d1b50d3..6a4e7873 100644 --- a/src/main/java/com/book/backend/domain/user/repository/UserRepository.java +++ b/src/main/java/com/book/backend/domain/user/repository/UserRepository.java @@ -10,8 +10,6 @@ public interface UserRepository extends JpaRepository { Optional findById(final Long id); - Optional findByLoginId(final String loginId); - Optional findByKakaoId(final String kakaoId); Optional findByAppleId(final String appleId); diff --git a/src/main/java/com/book/backend/domain/user/service/UserService.java b/src/main/java/com/book/backend/domain/user/service/UserService.java index a5d4def9..01876f45 100644 --- a/src/main/java/com/book/backend/domain/user/service/UserService.java +++ b/src/main/java/com/book/backend/domain/user/service/UserService.java @@ -42,26 +42,16 @@ public User loadLoggedinUser() { public User findByUsername(String username) { log.trace("UserService > findByUsername()"); - // TODO: 리팩토링 필요 try { - return findByLoginId(username); + return findByKakaoId(username); } catch (IllegalArgumentException e1) { try { - return findByKakaoId(username); + return findByAppleId(username); } catch (IllegalArgumentException e2) { - try { - return findByAppleId(username); - } catch (IllegalArgumentException e3) { - return null; - } + return null; } } - } - private User findByLoginId(String loginId) { - log.trace("UserService > findByLoginId()"); - return userRepository.findByLoginId(loginId) - .orElseThrow(IllegalArgumentException::new); } private User findByKakaoId(String kakaoId) { diff --git a/src/main/java/com/book/backend/global/SecurityConfig.java b/src/main/java/com/book/backend/global/SecurityConfig.java index ed53f6e7..2daa1f51 100644 --- a/src/main/java/com/book/backend/global/SecurityConfig.java +++ b/src/main/java/com/book/backend/global/SecurityConfig.java @@ -12,8 +12,6 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @@ -60,9 +58,4 @@ AuthenticationManager authenticationManager(AuthenticationConfiguration authenti return authenticationConfiguration.getAuthenticationManager(); } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cc033e28..359a6259 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -32,7 +32,7 @@ spring: init: encoding: utf-8 mode: always - data-locations: classpath:sql/data.sql, classpath:sql/dummy.sql + data-locations: classpath:sql/data.sql data: redis: @@ -50,10 +50,6 @@ openapi: authKey: ${OPENAPI_AUTH_KEY} kakao: - restapiKey: ${RESTAPI_KEY} - redirectUri: ${SERVER_URL}/login/oauth2/kakao - tokenRequestUri: https://kauth.kakao.com/oauth/token - userInfoUri: https://kapi.kakao.com/v2/user/me publicKeyUri: https://kauth.kakao.com/.well-known/jwks.json apple: diff --git a/src/main/resources/sql/data.sql b/src/main/resources/sql/data.sql index 661dfce4..cd2c0cb7 100644 --- a/src/main/resources/sql/data.sql +++ b/src/main/resources/sql/data.sql @@ -1,14 +1,15 @@ -- 장르 초기화 (한국십진분류법(KDC) 6판 기준) USE booktalk; -CREATE TABLE IF NOT EXISTS genre ( - genre_id bigint auto_increment - primary key, - kdc_num varchar(255) null, - name varchar(255) null, - parent_genre_id bigint null, - constraint FKroi0p36nixht1l7b3hadih34l - foreign key (parent_genre_id) references genre (genre_id) -)engine=InnoDB DEFAULT CHARSET=utf8; #한글 인코딩 +CREATE TABLE IF NOT EXISTS genre +( + genre_id bigint auto_increment + primary key, + parent_genre_id bigint null, + kdc_num varchar(255) null, + name varchar(255) null, + constraint FKroi0p36nixht1l7b3hadih34l + foreign key (parent_genre_id) references genre (genre_id) +); INSERT IGNORE INTO genre (parent_genre_id, kdc_num, name) values diff --git a/src/main/resources/sql/dummy.sql b/src/main/resources/sql/dummy.sql index 2ef75e6e..ead20d0b 100644 --- a/src/main/resources/sql/dummy.sql +++ b/src/main/resources/sql/dummy.sql @@ -1,222 +1,241 @@ USE booktalk; -CREATE TABLE IF NOT EXISTS book -( - book_id bigint auto_increment - primary key, - isbn varchar(255) null, - genre_id bigint null, - constraint FK2wrw92qged8jq0ucjtt972pbc - foreign key (genre_id) references genre (genre_id) -); -CREATE TABLE IF NOT EXISTS opentalk -( - opentalk_id bigint auto_increment - primary key, - book_id bigint not null, - constraint UK2xjdoiqspesxa8oenlmyt52us - unique (book_id), - constraint FK3v2bbp8k2bt96wybtbk6pv9yp - foreign key (book_id) references book (book_id) -); CREATE TABLE IF NOT EXISTS user ( - user_id bigint auto_increment - primary key, birth_date date null, gender tinyint null, - kakao_id varchar(255) null, - login_id varchar(255) null, - nickname varchar(255) null, - password varchar(255) null, reg_date datetime(6) null, - email varchar(255) null, - phone varchar(255) null, + user_id bigint auto_increment + primary key, apple_id varchar(255) null, + kakao_id varchar(255) null, + nickname varchar(255) null, constraint UK792t1v1e9f43yusiryq37re13 unique (kakao_id), + constraint UKah2bnumq4fnjg70axxky0j5xs + unique (nickname), constraint UKmnjg735lsf6wola6yyxxfj08l unique (apple_id), - constraint UKns0jdvuknugj5tmxq82un8q1x - unique (login_id), check (`gender` between 0 and 2) ); -CREATE TABLE IF NOT EXISTS user_opentalk +CREATE TABLE IF NOT EXISTS book ( - user_opentalk_id bigint auto_increment + book_id bigint auto_increment + primary key, + isbn varchar(255) null +); +CREATE TABLE IF NOT EXISTS opentalk +( + book_id bigint not null, + opentalk_id bigint auto_increment primary key, - opentalk_id bigint null, - user_id bigint null, - constraint FK1klbjl6843fae6vli3yu88s0a - foreign key (opentalk_id) references opentalk (opentalk_id), - constraint FKhri45w7pin1p8n1drbpiy0bgh - foreign key (user_id) references user (user_id) + constraint UK2xjdoiqspesxa8oenlmyt52us + unique (book_id), + constraint FK3v2bbp8k2bt96wybtbk6pv9yp + foreign key (book_id) references book (book_id) ); CREATE TABLE IF NOT EXISTS message ( + created_at datetime(6) null, message_id bigint auto_increment primary key, - content varchar(255) null, - created_at datetime(6) null, opentalk_id bigint null, user_id bigint null, + content varchar(255) null, constraint FK1iwnhnomrpim1phe1x0qlnva7 foreign key (opentalk_id) references opentalk (opentalk_id), constraint FK2op594yomeg261726h4dj75jq foreign key (user_id) references user (user_id) -) engine=InnoDB DEFAULT CHARSET=utf8; - - +); +create index idx_message_createdAt + on message (created_at); +CREATE TABLE IF NOT EXISTS `user dibs_books` +( + user_id bigint not null, + book_imageurl varchar(255) null, + bookname varchar(255) null, + isbn varchar(255) null, + constraint FKjhhvi9pyxkt4w4ysu9o0b858x + foreign key (user_id) references user (user_id) +); -INSERT IGNORE INTO book (book_id, isbn) -VALUES (1, '9788956055466'), - (2, '9788994120966'), - (3, '9788936433673'), - (4, '9788956604992'), - (5, '9788936434267'), - (6, '9788965700609'), - (7, '9788995151204'), - (8, '9788954622035'), - (9, '9788936433871'), - (10, '9791195522125'); +CREATE TABLE IF NOT EXISTS user_libraries +( + user_id bigint not null, + code varchar(255) null, + name varchar(255) null, + constraint FKcgk06cv5efj863hr3ffsa45r4 + foreign key (user_id) references user (user_id) +); +CREATE TABLE IF NOT EXISTS user_opentalk +( + opentalk_id bigint null, + user_id bigint null, + user_opentalk_id bigint auto_increment + primary key, + constraint FK1klbjl6843fae6vli3yu88s0a + foreign key (opentalk_id) references opentalk (opentalk_id), + constraint FKhri45w7pin1p8n1drbpiy0bgh + foreign key (user_id) references user (user_id) +); +CREATE TABLE IF NOT EXISTS user_read_books +( + user_id bigint not null, + book_imageurl varchar(255) null, + bookname varchar(255) null, + isbn varchar(255) null, + constraint FKks39a5vkr0flfyih9t7qpl88n + foreign key (user_id) references user (user_id) +); -INSERT IGNORE INTO opentalk (opentalk_id, book_id) -VALUES (1, 1), - (2, 2), - (3, 3), - (4, 4), - (5, 5), - (6, 6), - (7, 7), - (8, 8), - (9, 9), - (10, 10); + INSERT IGNORE INTO book (book_id, isbn) + VALUES (1, '9788956055466'), + (2, '9788994120966'), + (3, '9788936433673'), + (4, '9788956604992'), + (5, '9788936434267'), + (6, '9788965700609'), + (7, '9788995151204'), + (8, '9788954622035'), + (9, '9788936433871'), + (10, '9791195522125'); + INSERT IGNORE INTO opentalk (opentalk_id, book_id) + VALUES (1, 1), + (2, 2), + (3, 3), + (4, 4), + (5, 5), + (6, 6), + (7, 7), + (8, 8), + (9, 9), + (10, 10); -INSERT IGNORE INTO user (user_id, login_id, password, gender, birth_date, reg_date) -values (1, 'user1', 'useruser1!', '1', '1990-01-01', '2024-06-10'), - (2, 'user2', 'useruser2!', '2', '2000-03-16', '2024-07-22'), - (3, 'user3', 'useruser3!', '1', '1995-05-20', '2024-08-15'), - (4, 'user4', 'useruser4!', '2', '1992-07-25', '2024-09-30'), - (5, 'user5', 'useruser5!', '1', '2003-09-30', '2024-10-10'); + INSERT IGNORE INTO user (user_id, login_id, password, gender, birth_date, reg_date) + values (1, 'user1', 'useruser1!', '1', '1990-01-01', '2024-06-10'), + (2, 'user2', 'useruser2!', '2', '2000-03-16', '2024-07-22'), + (3, 'user3', 'useruser3!', '1', '1995-05-20', '2024-08-15'), + (4, 'user4', 'useruser4!', '2', '1992-07-25', '2024-09-30'), + (5, 'user5', 'useruser5!', '1', '2003-09-30', '2024-10-10'); -# 즐찾 오픈톡 -INSERT IGNORE INTO user_opentalk(user_opentalk_id, user_id, opentalk_id) -values (1, 1, 1), - (2, 1, 2), - (3, 2, 2), - (4, 2, 4), - (5, 2, 5); + # 즐찾 오픈톡 + INSERT IGNORE INTO user_opentalk(user_opentalk_id, user_id, opentalk_id) + values (1, 1, 1), + (2, 1, 2), + (3, 2, 2), + (4, 2, 4), + (5, 2, 5); -INSERT IGNORE INTO message (message_id, opentalk_id, user_id, content, created_at) -VALUES (1, 1, 1, '안녕하세요! 오늘 읽은 책이 정말 흥미로웠어요.', '2024-06-10 00:00:00'), - (2, 1, 2, '저도 그 책을 읽었어요. 특히 중반부가 인상 깊었어요.', '2024-06-10 00:01:00'), - (3, 1, 3, '저는 그 책에서 얻은 통찰력이 정말 유익했어요.', '2024-06-10 00:02:00'), - (4, 5, 4, '그 책의 결말이 정말 놀라웠죠!', '2024-06-10 00:03:00'), - (5, 5, 5, '맞아요! 그 결말을 보고 한동안 생각이 멈추지 않았어요.', '2024-06-10 00:04:00'), - (6, 1, 2, '작가는 정말 대단한 사람인 것 같아요.', '2024-06-10 00:05:00'), - (7, 3, 3, '저도 동의해요. 그의 글쓰기 스타일이 독특해요.', '2024-06-10 00:06:00'), - (8, 8, 4, '그렇죠? 다음 책이 정말 기대돼요.', '2024-06-10 00:07:00'), - (9, 8, 5, '저도 기다리고 있어요! 언제 나올까요?', '2024-06-10 00:08:00'), - (10, 8, 1, '출시일이 곧 발표된다고 들었어요.', '2024-06-10 00:09:00'), - (11, 10, 2, '책을 읽으며 느낀 점이 정말 많아요.', '2024-06-10 00:10:00'), - (12, 10, 3, '맞아요. 제 인생에 큰 영향을 줬어요.', '2024-06-10 00:11:00'), - (13, 1, 4, '책에서 다룬 주제가 현실적이어서 더 와닿았어요.', '2024-06-10 00:12:00'), - (14, 4, 5, '맞아요. 현실적인 문제들을 잘 다루었죠.', '2024-06-10 00:13:00'), - (15, 2, 1, '그 부분이 정말 마음에 들었어요.', '2024-06-10 00:14:00'), - (16, 2, 2, '다음 책에서는 더 깊이 다뤘으면 좋겠어요.', '2024-06-10 00:15:00'), - (17, 2, 3, '그러게요. 더 깊이 있는 내용이 나오면 좋겠어요.', '2024-06-10 00:16:00'), - (18, 3, 4, '다음 책은 언제쯤 나올까요?', '2024-06-10 00:17:00'), - (19, 4, 5, '곧 발표될 거라고 들었어요.', '2024-06-10 00:18:00'), - (20, 5, 1, '빨리 나왔으면 좋겠어요!', '2024-06-10 00:19:00'), - (21, 1, 1, '안녕하세요. 최근에 읽은 책이 있나요?', '2024-06-10 00:20:00'), - (22, 2, 2, '네, 저는 "해리포터"를 다시 읽었어요.', '2024-06-10 00:21:00'), - (23, 10, 3, '아, 그 책 정말 재밌죠. 저는 지금 "반지의 제왕"을 읽고 있어요.', '2024-06-10 00:22:00'), - (24, 9, 4, '저도 "반지의 제왕" 좋아해요! 특히 영화도 멋지더라고요.', '2024-06-10 00:23:00'), - (25, 9, 5, '맞아요. 그 영화는 정말 장관이었죠.', '2024-06-10 00:24:00'), - (26, 1, 2, '요즘에는 어떤 책을 읽고 계신가요?', '2024-06-10 00:25:00'), - (27, 1, 3, '저는 "호밀밭의 파수꾼"을 읽고 있어요. 분위기가 독특하네요.', '2024-06-10 00:30:00'), - (28, 3, 4, '그 책은 좀 어두운 면이 있지만 생각할 거리를 많이 주죠.', '2024-06-10 00:35:00'), - (29, 2, 5, '네, 주인공의 심리 상태가 흥미로워요.', '2024-06-10 00:40:00'), - (30, 2, 1, '최근에는 "셜록 홈즈" 시리즈를 읽고 있어요.', '2024-06-10 00:45:00'), - (31, 1, 2, '셜록 홈즈는 클래식이죠. 사건의 전개가 정말 흥미롭잖아요.', '2024-06-10 00:50:00'), - (32, 2, 3, '맞아요. 매번 결말이 예상치 못한 방향으로 가서 놀라워요.', '2024-06-10 00:55:00'), - (33, 3, 4, '혹시 탐정 소설 중에 추천할 만한 책이 있을까요?', '2024-06-10 01:00:00'), - (34, 4, 5, '저는 "아가사 크리스티"의 작품들을 추천드려요.', '2024-06-10 01:05:00'), - (35, 5, 1, '그분의 작품은 정말 수수께끼 같죠.', '2024-06-10 01:10:00'), - (36, 5, 3, '특히 "오리엔트 특급 살인"은 최고예요.', '2024-06-10 01:15:00'), - (37, 5, 4, '맞아요, 결말이 정말 충격적이었어요.', '2024-06-10 01:20:00'), - (38, 8, 5, '혹시 다른 추천할 만한 책은?', '2024-06-10 01:25:00'), - (39, 6, 1, '저는 "대지"라는 책을 추천합니다.', '2024-06-10 01:30:00'), - (40, 5, 2, '"대지"는 어떤 내용인가요?', '2024-06-10 01:35:00'), - (41, 5, 3, '주로 중국 농민의 삶을 다루고 있어요. 정말 감동적이에요.', '2024-06-10 01:40:00'), - (42, 2, 4, '그렇군요. 한번 읽어봐야겠어요.', '2024-06-10 01:45:00'), - (43, 2, 5, '시간 되면 꼭 읽어보세요.', '2024-06-10 01:50:00'), - (44, 2, 1, '요즘 어떤 책을 읽고 있나요?', '2024-06-10 01:55:00'), - (45, 5, 2, '저는 "바람과 함께 사라지다"를 읽고 있어요.', '2024-06-10 02:00:00'), - (46, 1, 3, '그 책은 진짜 클래식이죠.', '2024-06-10 02:05:00'), - (47, 2, 4, '네, 정말 흡입력이 있어요.', '2024-06-10 02:10:00'), - (48, 3, 5, '주인공의 캐릭터가 정말 인상적이죠.', '2024-06-10 02:15:00'), - (49, 4, 1, '맞아요, 특히 스칼렛의 캐릭터가 강렬해요.', '2024-06-10 02:20:00'), - (50, 10, 2, '저는 사실 그 책을 읽고 눈물을 흘렸어요.', '2024-06-10 02:25:00'), - (51, 10, 3, '그만큼 감동적인 책이죠.', '2024-06-10 02:30:00'), - (52, 10, 4, '다른 추천할 만한 책이 있나요?', '2024-06-10 02:35:00'), - (53, 10, 5, '저는 "모비딕"을 추천합니다.', '2024-06-10 02:40:00'), - (54, 10, 1, '그 책은 정말 심오하죠.', '2024-06-10 02:45:00'), - (55, 9, 2, '주인공의 집념이 대단해요.', '2024-06-10 02:50:00'), - (56, 9, 3, '맞아요, 정말 깊이 생각하게 만드는 책이에요.', '2024-06-10 02:55:00'), - (57, 9, 4, '어떤 책을 읽고 계신가요?', '2024-06-10 03:00:00'), - (58, 9, 5, '저는 "파리대왕"을 읽고 있어요.', '2024-06-10 03:05:00'), - (59, 9, 1, '그 책은 정말 충격적이죠.', '2024-06-10 03:10:00'), - (60, 9, 2, '네, 인간 본성에 대해 다시 생각하게 돼요.', '2024-06-10 03:15:00'), - (61, 10, 3, '특히 마지막 장면이 잊혀지질 않아요.', '2024-06-10 03:20:00'), - (62, 10, 4, '맞아요, 정말 강렬하죠.', '2024-06-10 03:25:00'), - (63, 3, 5, '또 다른 책 추천이 있을까요?', '2024-06-10 03:30:00'), - (64, 4, 1, '저는 "작은 아씨들"을 추천해요.', '2024-06-10 03:35:00'), - (65, 5, 2, '그 책은 정말 따뜻하죠.', '2024-06-10 03:40:00'), - (66, 6, 3, '가족의 사랑이 잘 표현된 책이에요.', '2024-06-10 03:45:00'), - (67, 6, 4, '네, 정말 감동적이었어요.', '2024-06-10 03:50:00'), - (68, 8, 5, '저는 사실 그 책을 읽고 행복해졌어요.', '2024-06-10 03:55:00'), - (69, 8, 1, '그만큼 따뜻한 책이죠.', '2024-06-10 04:00:00'), - (70, 7, 2, '요즘 또 다른 책을 읽고 계신가요?', '2024-06-10 04:05:00'), - (71, 7, 3, '저는 "셜록 홈즈" 시리즈에 빠져 있어요.', '2024-06-10 04:10:00'), - (72, 7, 1, '안녕하세요! 책 읽는 것을 좋아하세요?', '2024-06-10 04:12:00'), - (73, 7, 2, '네, 특히 소설을 좋아합니다. 최근에 읽은 책은 무엇인가요?', '2024-06-10 04:13:00'), - (74, 7, 3, '저는 최근에 "해리포터"를 다시 읽었어요.', '2024-06-10 04:14:00'), - (75, 7, 4, '해리포터 정말 좋죠! 저는 "반지의 제왕"도 좋아해요.', '2024-06-10 04:15:00'), - (76, 7, 5, '역시 판타지 장르가 최고인 것 같아요.', '2024-06-10 04:16:00'), - (77, 5, 2, '맞아요, 다음엔 "나니아 연대기"를 읽어볼까 해요.', '2024-06-10 04:17:00'), - (78, 6, 3, '좋은 선택이에요! 이야기의 전개가 정말 흥미로워요.', '2024-06-10 04:18:00'), - (79, 4, 4, '다들 다양한 책을 읽는 것 같아 정말 좋네요.', '2024-06-10 04:19:00'), - (80, 4, 5, '책을 읽으면 다른 세계에 빠져드는 것 같아요.', '2024-06-10 04:20:00'), - (81, 5, 1, '정말 그렇죠! 읽고 나면 생각도 깊어지는 것 같아요.', '2024-06-10 04:21:00'), - (82, 3, 2, '저도 그 책을 좋아해요. 전개가 정말 훌륭하죠.', '2024-06-10 07:31:00'), - (83, 4, 4, '이야기의 흐름이 매력적이에요. 중간에 멈출 수가 없었어요.', '2024-06-10 07:32:00'), - (84, 9, 1, '맞아요, 저도 시간 가는 줄 모르고 읽었어요.', '2024-06-10 07:33:00'), - (85, 9, 1, '안녕하세요, 오늘 어떤 책을 읽으셨나요?', '2024-06-10 07:35:00'), - (86, 10, 3, '저는 오늘 "자바의 정석"을 읽었어요.', '2024-06-10 07:36:00'), - (87, 10, 2, '오, 저도 그 책 좋아해요. 특히 5장의 내용이 인상적이었어요.', '2024-06-10 07:37:00'), - (88, 10, 4, '혹시 "클린 코드"도 읽어보셨나요?', '2024-06-10 07:38:00'), - (89, 8, 5, '네, 클린 코드는 정말 유익한 책이었어요.', '2024-06-10 07:39:00'), - (90, 7, 2, '요즘 읽고 있는 책은 무엇인가요?', '2024-06-10 07:40:00'), - (91, 6, 4, '저는 "도메인 주도 설계"를 읽고 있어요.', '2024-06-10 07:41:00'), - (92, 8, 1, '그 책도 좋죠. 설계 패턴에 대해 많은 것을 배울 수 있었어요.', '2024-06-10 07:42:00'), - (93, 8, 5, '최근에 추천받은 책이 있나요?', '2024-06-10 07:43:00'), - (94, 8, 3, '네, "Effective Java"를 추천받았어요.', '2024-06-10 07:44:00'), - (95, 6, 5, '저도 그 책 읽고 있어요! 정말 유익해요.', '2024-06-10 07:45:00'), - (96, 2, 2, '그 책을 통해 자바의 깊이를 더 이해하게 되었어요.', '2024-06-10 07:46:00'), - (97, 3, 4, '혹시 최근에 읽은 책 중에서 제일 기억에 남는 책은 무엇인가요?', '2024-06-10 07:47:00'), - (98, 4, 1, '"소프트웨어 아키텍처"라는 책이 정말 유익했어요.', '2024-06-10 07:48:00'), - (99, 5, 3, '맞아요, 그 책은 개발자라면 꼭 읽어야 할 책이죠.', '2024-06-10 07:49:00'), - (100, 1, 4, '혹시 다음에 읽고 싶은 책이 있나요?', '2024-06-10 07:50:00'), - (101, 2, 1, '"리팩토링"을 읽고 싶어요. 코드 개선에 많은 도움이 될 것 같아요.', '2024-06-10 07:51:00'), - (102, 3, 5, '그 책도 정말 유익해요. 실전에서 많이 참고하고 있어요.', '2024-06-10 07:52:00'), - (103, 5, 2, '자바와 관련된 좋은 책이 또 있을까요?', '2024-06-10 07:53:00'), - (104, 5, 3, '"자바 병렬 프로그래밍"도 추천할 만해요.', '2024-06-10 07:54:00'), - (105, 1, 5, '감사합니다. 다음에 꼭 읽어볼게요.', '2024-06-10 07:55:00'), - (106, 2, 4, '읽으면서 궁금한 점이 생기면 같이 토론해요.', '2024-06-10 07:56:00'), - (107, 10, 1, '좋아요! 함께 공부하는 게 더 재미있어요.', '2024-06-10 07:57:00'), - (108, 10, 2, '이렇게 책에 대해 이야기 나누는 시간이 정말 좋아요.', '2024-06-10 07:58:00'), - (109, 8, 3, '저도요, 서로에게 많은 도움이 되는 것 같아요.', '2024-06-10 07:59:00'), - (109, 8, 2, '오늘 하루도 화이팅입니다!', '2024-06-10 07:59:00'); + INSERT IGNORE INTO message (message_id, opentalk_id, user_id, content, created_at) + VALUES (1, 1, 1, '안녕하세요! 오늘 읽은 책이 정말 흥미로웠어요.', '2024-06-10 00:00:00'), + (2, 1, 2, '저도 그 책을 읽었어요. 특히 중반부가 인상 깊었어요.', '2024-06-10 00:01:00'), + (3, 1, 3, '저는 그 책에서 얻은 통찰력이 정말 유익했어요.', '2024-06-10 00:02:00'), + (4, 5, 4, '그 책의 결말이 정말 놀라웠죠!', '2024-06-10 00:03:00'), + (5, 5, 5, '맞아요! 그 결말을 보고 한동안 생각이 멈추지 않았어요.', '2024-06-10 00:04:00'), + (6, 1, 2, '작가는 정말 대단한 사람인 것 같아요.', '2024-06-10 00:05:00'), + (7, 3, 3, '저도 동의해요. 그의 글쓰기 스타일이 독특해요.', '2024-06-10 00:06:00'), + (8, 8, 4, '그렇죠? 다음 책이 정말 기대돼요.', '2024-06-10 00:07:00'), + (9, 8, 5, '저도 기다리고 있어요! 언제 나올까요?', '2024-06-10 00:08:00'), + (10, 8, 1, '출시일이 곧 발표된다고 들었어요.', '2024-06-10 00:09:00'), + (11, 10, 2, '책을 읽으며 느낀 점이 정말 많아요.', '2024-06-10 00:10:00'), + (12, 10, 3, '맞아요. 제 인생에 큰 영향을 줬어요.', '2024-06-10 00:11:00'), + (13, 1, 4, '책에서 다룬 주제가 현실적이어서 더 와닿았어요.', '2024-06-10 00:12:00'), + (14, 4, 5, '맞아요. 현실적인 문제들을 잘 다루었죠.', '2024-06-10 00:13:00'), + (15, 2, 1, '그 부분이 정말 마음에 들었어요.', '2024-06-10 00:14:00'), + (16, 2, 2, '다음 책에서는 더 깊이 다뤘으면 좋겠어요.', '2024-06-10 00:15:00'), + (17, 2, 3, '그러게요. 더 깊이 있는 내용이 나오면 좋겠어요.', '2024-06-10 00:16:00'), + (18, 3, 4, '다음 책은 언제쯤 나올까요?', '2024-06-10 00:17:00'), + (19, 4, 5, '곧 발표될 거라고 들었어요.', '2024-06-10 00:18:00'), + (20, 5, 1, '빨리 나왔으면 좋겠어요!', '2024-06-10 00:19:00'), + (21, 1, 1, '안녕하세요. 최근에 읽은 책이 있나요?', '2024-06-10 00:20:00'), + (22, 2, 2, '네, 저는 "해리포터"를 다시 읽었어요.', '2024-06-10 00:21:00'), + (23, 10, 3, '아, 그 책 정말 재밌죠. 저는 지금 "반지의 제왕"을 읽고 있어요.', '2024-06-10 00:22:00'), + (24, 9, 4, '저도 "반지의 제왕" 좋아해요! 특히 영화도 멋지더라고요.', '2024-06-10 00:23:00'), + (25, 9, 5, '맞아요. 그 영화는 정말 장관이었죠.', '2024-06-10 00:24:00'), + (26, 1, 2, '요즘에는 어떤 책을 읽고 계신가요?', '2024-06-10 00:25:00'), + (27, 1, 3, '저는 "호밀밭의 파수꾼"을 읽고 있어요. 분위기가 독특하네요.', '2024-06-10 00:30:00'), + (28, 3, 4, '그 책은 좀 어두운 면이 있지만 생각할 거리를 많이 주죠.', '2024-06-10 00:35:00'), + (29, 2, 5, '네, 주인공의 심리 상태가 흥미로워요.', '2024-06-10 00:40:00'), + (30, 2, 1, '최근에는 "셜록 홈즈" 시리즈를 읽고 있어요.', '2024-06-10 00:45:00'), + (31, 1, 2, '셜록 홈즈는 클래식이죠. 사건의 전개가 정말 흥미롭잖아요.', '2024-06-10 00:50:00'), + (32, 2, 3, '맞아요. 매번 결말이 예상치 못한 방향으로 가서 놀라워요.', '2024-06-10 00:55:00'), + (33, 3, 4, '혹시 탐정 소설 중에 추천할 만한 책이 있을까요?', '2024-06-10 01:00:00'), + (34, 4, 5, '저는 "아가사 크리스티"의 작품들을 추천드려요.', '2024-06-10 01:05:00'), + (35, 5, 1, '그분의 작품은 정말 수수께끼 같죠.', '2024-06-10 01:10:00'), + (36, 5, 3, '특히 "오리엔트 특급 살인"은 최고예요.', '2024-06-10 01:15:00'), + (37, 5, 4, '맞아요, 결말이 정말 충격적이었어요.', '2024-06-10 01:20:00'), + (38, 8, 5, '혹시 다른 추천할 만한 책은?', '2024-06-10 01:25:00'), + (39, 6, 1, '저는 "대지"라는 책을 추천합니다.', '2024-06-10 01:30:00'), + (40, 5, 2, '"대지"는 어떤 내용인가요?', '2024-06-10 01:35:00'), + (41, 5, 3, '주로 중국 농민의 삶을 다루고 있어요. 정말 감동적이에요.', '2024-06-10 01:40:00'), + (42, 2, 4, '그렇군요. 한번 읽어봐야겠어요.', '2024-06-10 01:45:00'), + (43, 2, 5, '시간 되면 꼭 읽어보세요.', '2024-06-10 01:50:00'), + (44, 2, 1, '요즘 어떤 책을 읽고 있나요?', '2024-06-10 01:55:00'), + (45, 5, 2, '저는 "바람과 함께 사라지다"를 읽고 있어요.', '2024-06-10 02:00:00'), + (46, 1, 3, '그 책은 진짜 클래식이죠.', '2024-06-10 02:05:00'), + (47, 2, 4, '네, 정말 흡입력이 있어요.', '2024-06-10 02:10:00'), + (48, 3, 5, '주인공의 캐릭터가 정말 인상적이죠.', '2024-06-10 02:15:00'), + (49, 4, 1, '맞아요, 특히 스칼렛의 캐릭터가 강렬해요.', '2024-06-10 02:20:00'), + (50, 10, 2, '저는 사실 그 책을 읽고 눈물을 흘렸어요.', '2024-06-10 02:25:00'), + (51, 10, 3, '그만큼 감동적인 책이죠.', '2024-06-10 02:30:00'), + (52, 10, 4, '다른 추천할 만한 책이 있나요?', '2024-06-10 02:35:00'), + (53, 10, 5, '저는 "모비딕"을 추천합니다.', '2024-06-10 02:40:00'), + (54, 10, 1, '그 책은 정말 심오하죠.', '2024-06-10 02:45:00'), + (55, 9, 2, '주인공의 집념이 대단해요.', '2024-06-10 02:50:00'), + (56, 9, 3, '맞아요, 정말 깊이 생각하게 만드는 책이에요.', '2024-06-10 02:55:00'), + (57, 9, 4, '어떤 책을 읽고 계신가요?', '2024-06-10 03:00:00'), + (58, 9, 5, '저는 "파리대왕"을 읽고 있어요.', '2024-06-10 03:05:00'), + (59, 9, 1, '그 책은 정말 충격적이죠.', '2024-06-10 03:10:00'), + (60, 9, 2, '네, 인간 본성에 대해 다시 생각하게 돼요.', '2024-06-10 03:15:00'), + (61, 10, 3, '특히 마지막 장면이 잊혀지질 않아요.', '2024-06-10 03:20:00'), + (62, 10, 4, '맞아요, 정말 강렬하죠.', '2024-06-10 03:25:00'), + (63, 3, 5, '또 다른 책 추천이 있을까요?', '2024-06-10 03:30:00'), + (64, 4, 1, '저는 "작은 아씨들"을 추천해요.', '2024-06-10 03:35:00'), + (65, 5, 2, '그 책은 정말 따뜻하죠.', '2024-06-10 03:40:00'), + (66, 6, 3, '가족의 사랑이 잘 표현된 책이에요.', '2024-06-10 03:45:00'), + (67, 6, 4, '네, 정말 감동적이었어요.', '2024-06-10 03:50:00'), + (68, 8, 5, '저는 사실 그 책을 읽고 행복해졌어요.', '2024-06-10 03:55:00'), + (69, 8, 1, '그만큼 따뜻한 책이죠.', '2024-06-10 04:00:00'), + (70, 7, 2, '요즘 또 다른 책을 읽고 계신가요?', '2024-06-10 04:05:00'), + (71, 7, 3, '저는 "셜록 홈즈" 시리즈에 빠져 있어요.', '2024-06-10 04:10:00'), + (72, 7, 1, '안녕하세요! 책 읽는 것을 좋아하세요?', '2024-06-10 04:12:00'), + (73, 7, 2, '네, 특히 소설을 좋아합니다. 최근에 읽은 책은 무엇인가요?', '2024-06-10 04:13:00'), + (74, 7, 3, '저는 최근에 "해리포터"를 다시 읽었어요.', '2024-06-10 04:14:00'), + (75, 7, 4, '해리포터 정말 좋죠! 저는 "반지의 제왕"도 좋아해요.', '2024-06-10 04:15:00'), + (76, 7, 5, '역시 판타지 장르가 최고인 것 같아요.', '2024-06-10 04:16:00'), + (77, 5, 2, '맞아요, 다음엔 "나니아 연대기"를 읽어볼까 해요.', '2024-06-10 04:17:00'), + (78, 6, 3, '좋은 선택이에요! 이야기의 전개가 정말 흥미로워요.', '2024-06-10 04:18:00'), + (79, 4, 4, '다들 다양한 책을 읽는 것 같아 정말 좋네요.', '2024-06-10 04:19:00'), + (80, 4, 5, '책을 읽으면 다른 세계에 빠져드는 것 같아요.', '2024-06-10 04:20:00'), + (81, 5, 1, '정말 그렇죠! 읽고 나면 생각도 깊어지는 것 같아요.', '2024-06-10 04:21:00'), + (82, 3, 2, '저도 그 책을 좋아해요. 전개가 정말 훌륭하죠.', '2024-06-10 07:31:00'), + (83, 4, 4, '이야기의 흐름이 매력적이에요. 중간에 멈출 수가 없었어요.', '2024-06-10 07:32:00'), + (84, 9, 1, '맞아요, 저도 시간 가는 줄 모르고 읽었어요.', '2024-06-10 07:33:00'), + (85, 9, 1, '안녕하세요, 오늘 어떤 책을 읽으셨나요?', '2024-06-10 07:35:00'), + (86, 10, 3, '저는 오늘 "자바의 정석"을 읽었어요.', '2024-06-10 07:36:00'), + (87, 10, 2, '오, 저도 그 책 좋아해요. 특히 5장의 내용이 인상적이었어요.', '2024-06-10 07:37:00'), + (88, 10, 4, '혹시 "클린 코드"도 읽어보셨나요?', '2024-06-10 07:38:00'), + (89, 8, 5, '네, 클린 코드는 정말 유익한 책이었어요.', '2024-06-10 07:39:00'), + (90, 7, 2, '요즘 읽고 있는 책은 무엇인가요?', '2024-06-10 07:40:00'), + (91, 6, 4, '저는 "도메인 주도 설계"를 읽고 있어요.', '2024-06-10 07:41:00'), + (92, 8, 1, '그 책도 좋죠. 설계 패턴에 대해 많은 것을 배울 수 있었어요.', '2024-06-10 07:42:00'), + (93, 8, 5, '최근에 추천받은 책이 있나요?', '2024-06-10 07:43:00'), + (94, 8, 3, '네, "Effective Java"를 추천받았어요.', '2024-06-10 07:44:00'), + (95, 6, 5, '저도 그 책 읽고 있어요! 정말 유익해요.', '2024-06-10 07:45:00'), + (96, 2, 2, '그 책을 통해 자바의 깊이를 더 이해하게 되었어요.', '2024-06-10 07:46:00'), + (97, 3, 4, '혹시 최근에 읽은 책 중에서 제일 기억에 남는 책은 무엇인가요?', '2024-06-10 07:47:00'), + (98, 4, 1, '"소프트웨어 아키텍처"라는 책이 정말 유익했어요.', '2024-06-10 07:48:00'), + (99, 5, 3, '맞아요, 그 책은 개발자라면 꼭 읽어야 할 책이죠.', '2024-06-10 07:49:00'), + (100, 1, 4, '혹시 다음에 읽고 싶은 책이 있나요?', '2024-06-10 07:50:00'), + (101, 2, 1, '"리팩토링"을 읽고 싶어요. 코드 개선에 많은 도움이 될 것 같아요.', '2024-06-10 07:51:00'), + (102, 3, 5, '그 책도 정말 유익해요. 실전에서 많이 참고하고 있어요.', '2024-06-10 07:52:00'), + (103, 5, 2, '자바와 관련된 좋은 책이 또 있을까요?', '2024-06-10 07:53:00'), + (104, 5, 3, '"자바 병렬 프로그래밍"도 추천할 만해요.', '2024-06-10 07:54:00'), + (105, 1, 5, '감사합니다. 다음에 꼭 읽어볼게요.', '2024-06-10 07:55:00'), + (106, 2, 4, '읽으면서 궁금한 점이 생기면 같이 토론해요.', '2024-06-10 07:56:00'), + (107, 10, 1, '좋아요! 함께 공부하는 게 더 재미있어요.', '2024-06-10 07:57:00'), + (108, 10, 2, '이렇게 책에 대해 이야기 나누는 시간이 정말 좋아요.', '2024-06-10 07:58:00'), + (109, 8, 3, '저도요, 서로에게 많은 도움이 되는 것 같아요.', '2024-06-10 07:59:00'), + (109, 8, 2, '오늘 하루도 화이팅입니다!', '2024-06-10 07:59:00');