From 3b06b557893478a21c0ea013220efd0b94530dd6 Mon Sep 17 00:00:00 2001 From: 5jisoo <56earls@gmail.com> Date: Sun, 6 Aug 2023 20:54:45 +0900 Subject: [PATCH 01/10] #7 feat: search for exhibitions by name when uploading a story --- .../dto/SearchExhibitionsByNameResDto.java | 39 +++++++++++++++++++ .../repository/ExhibitionRepository.java | 4 ++ .../story/controller/StoryController.java | 14 +++++++ .../umc/place/story/service/StoryService.java | 16 ++++++++ 4 files changed, 73 insertions(+) create mode 100644 place/src/main/java/com/umc/place/exhibition/dto/SearchExhibitionsByNameResDto.java diff --git a/place/src/main/java/com/umc/place/exhibition/dto/SearchExhibitionsByNameResDto.java b/place/src/main/java/com/umc/place/exhibition/dto/SearchExhibitionsByNameResDto.java new file mode 100644 index 0000000..9891824 --- /dev/null +++ b/place/src/main/java/com/umc/place/exhibition/dto/SearchExhibitionsByNameResDto.java @@ -0,0 +1,39 @@ +package com.umc.place.exhibition.dto; + +import com.umc.place.exhibition.entity.Exhibition; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.domain.Page; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Data +@NoArgsConstructor +public class SearchExhibitionsByNameResDto { + + private List searchedExhibitions = new ArrayList(); + + @Builder + public SearchExhibitionsByNameResDto(Page exhibitions) { + this.searchedExhibitions + = exhibitions.stream() + .map(exhibition -> new SearchedExhibitionByName(exhibition)) + .collect(Collectors.toList()); + } + + @Data + @NoArgsConstructor + public static class SearchedExhibitionByName { + private Long exhibitionIdx; + private String exhibitionName; + + @Builder + public SearchedExhibitionByName(Exhibition exhibition) { + this.exhibitionIdx = exhibition.getExhibitionIdx(); + this.exhibitionName = exhibition.getExhibitionName(); + } + } +} diff --git a/place/src/main/java/com/umc/place/exhibition/repository/ExhibitionRepository.java b/place/src/main/java/com/umc/place/exhibition/repository/ExhibitionRepository.java index ebc4428..f87bd22 100644 --- a/place/src/main/java/com/umc/place/exhibition/repository/ExhibitionRepository.java +++ b/place/src/main/java/com/umc/place/exhibition/repository/ExhibitionRepository.java @@ -13,7 +13,9 @@ @Repository public interface ExhibitionRepository extends JpaRepository { Page findByCategory(Category category, Pageable pageable); // 카테고리 기반 전체 조회(페이징) + Page findAll(Pageable pageable); // 전체 조회(페이징) + boolean existsByCategory(Category category); @Query("select case when count(e) > 0 then true else false end from Exhibition e where Function('replace', e.location, ' ', '') like %:location%") @@ -25,4 +27,6 @@ public interface ExhibitionRepository extends JpaRepository { @Query("select e from Exhibition e where Function('replace', e.location, ' ', '') like %:location%") Page findByLocationLike(@Param("location") String location, Pageable pageable); + + Page findByExhibitionNameContainingOrderByExhibitionName(String searchKeyword, Pageable pageable); } diff --git a/place/src/main/java/com/umc/place/story/controller/StoryController.java b/place/src/main/java/com/umc/place/story/controller/StoryController.java index 5dfd3eb..d88c271 100644 --- a/place/src/main/java/com/umc/place/story/controller/StoryController.java +++ b/place/src/main/java/com/umc/place/story/controller/StoryController.java @@ -2,11 +2,14 @@ import com.umc.place.common.BaseException; import com.umc.place.common.BaseResponse; +import com.umc.place.exhibition.dto.SearchExhibitionsByNameResDto; import com.umc.place.story.dto.StoryDetailResponseDto; import com.umc.place.story.dto.StoryUploadRequestDto; import com.umc.place.story.dto.StoryUploadResponseDto; import com.umc.place.story.service.StoryService; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; @RestController @@ -34,4 +37,15 @@ public BaseResponse getStoryDetail(@PathVariable Long st return new BaseResponse<>(e.getStatus()); } } + + @GetMapping("/search") + public BaseResponse getExhibitionWhenUploadStory( + @PageableDefault(size = 5) Pageable pageable, + @RequestParam(required = false) String searchWord) { + try { + return new BaseResponse<>(storyService.searchExhibitionByName(searchWord, pageable)); + } catch (BaseException e) { + return new BaseResponse<>(e.getStatus()); + } + } } diff --git a/place/src/main/java/com/umc/place/story/service/StoryService.java b/place/src/main/java/com/umc/place/story/service/StoryService.java index 7558dc4..4977f10 100644 --- a/place/src/main/java/com/umc/place/story/service/StoryService.java +++ b/place/src/main/java/com/umc/place/story/service/StoryService.java @@ -2,6 +2,7 @@ import com.umc.place.comment.dto.CommentResDto; import com.umc.place.common.BaseException; +import com.umc.place.exhibition.dto.SearchExhibitionsByNameResDto; import com.umc.place.exhibition.entity.Exhibition; import com.umc.place.exhibition.repository.ExhibitionRepository; import com.umc.place.story.dto.StoryDetailResponseDto; @@ -15,6 +16,8 @@ import com.umc.place.user.entity.User; import com.umc.place.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -76,6 +79,19 @@ public StoryDetailResponseDto getStoryDetail(Long storyIdx, Long userId) throws } } + public SearchExhibitionsByNameResDto searchExhibitionByName(String searchWord, Pageable page) throws BaseException { + try { + searchWord = searchWord.trim(); + Page searchedExhibitions + = exhibitionRepository.findByExhibitionNameContainingOrderByExhibitionName(searchWord, page); + return SearchExhibitionsByNameResDto.builder() + .exhibitions(searchedExhibitions) + .build(); + } catch (Exception e) { + throw new BaseException(DATABASE_ERROR); + } + } + @Transactional public StoryUploadResponseDto uploadStory(StoryUploadRequestDto storyUploadRequestDto, Long userId) throws BaseException { try { From 8a3cda8a2b25bb98d37c257d48faef18741db802 Mon Sep 17 00:00:00 2001 From: ri-naa Date: Wed, 2 Aug 2023 02:26:37 +0900 Subject: [PATCH 02/10] =?UTF-8?q?#13=20feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/OAuth2/dto/KakaoTokenResponse.java | 13 +++ .../user/OAuth2/service/KakaoAuthService.java | 81 ++++++++++++++ .../place/user/service/JwtTokenService.java | 102 ++++++++++++++++++ .../umc/place/user/service/UserService.java | 92 +++++++++++++++- 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 place/src/main/java/com/umc/place/user/OAuth2/dto/KakaoTokenResponse.java create mode 100644 place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java create mode 100644 place/src/main/java/com/umc/place/user/service/JwtTokenService.java diff --git a/place/src/main/java/com/umc/place/user/OAuth2/dto/KakaoTokenResponse.java b/place/src/main/java/com/umc/place/user/OAuth2/dto/KakaoTokenResponse.java new file mode 100644 index 0000000..b63ec19 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/OAuth2/dto/KakaoTokenResponse.java @@ -0,0 +1,13 @@ +package com.umc.place.user.OAuth2.dto; + +import lombok.Data; + +@Data +public class KakaoTokenResponse { + private String token_type; + private String access_token; + private int expires_in; + private String refresh_token; + private int refresh_token_expires_in; + private String scope; +} diff --git a/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java new file mode 100644 index 0000000..9717b0a --- /dev/null +++ b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java @@ -0,0 +1,81 @@ +package com.umc.place.user.OAuth2.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.umc.place.user.OAuth2.dto.KakaoTokenResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +@Service +@Transactional +@Slf4j +public class KakaoAuthService { + + @Value("${spring.security.oauth2.client.registration.google.client-id}") + private String kakaoClientId; + + //@Value("${spring.security.oauth2.client.registration.google.client-secret}") + //private String kakaoClientSecret; + + @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + private String kakaoRedirectUri; + + @Value("${spring.OAuth2.google.url.token}") + private String KAKAO_TOKEN_REQUEST_URL; + + @Value("${spring.OAuth2.google.url.profile}") + private String KAKAO_USERINFO_REQUEST_URL; + + //인가코드로 카카오 토큰 발급받기 + public KakaoTokenResponse getKakaoToken (String authorizationCode) throws JsonProcessingException { + Map params = new HashMap<>(); + params.put("code", authorizationCode); + params.put("client_id", kakaoClientId); + params.put("redirect_uri", kakaoRedirectUri); + params.put("grant_type", "authorization_code"); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.postForEntity(KAKAO_TOKEN_REQUEST_URL, + params, String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + KakaoTokenResponse kakaoOAuthToken = objectMapper.readValue(response.getBody(), KakaoTokenResponse.class); + return kakaoOAuthToken; + } + + //카카오 토큰으로 사용자 정보(Long userIdx) 가져오기 + public long getUserIdx(KakaoTokenResponse kakaoToken) throws JsonProcessingException { + + //header에 accessToken 담기 + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + kakaoToken.getAccess_token()); + + HttpEntity> request = new HttpEntity<>(headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity response = restTemplate.exchange(KAKAO_USERINFO_REQUEST_URL, HttpMethod.GET, request, String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response.getBody()); + + //user id 가져오기 + String id = jsonNode.get("id").asText(); + long userIdx = Long.parseLong(id); + + return userIdx; + } + +} + diff --git a/place/src/main/java/com/umc/place/user/service/JwtTokenService.java b/place/src/main/java/com/umc/place/user/service/JwtTokenService.java new file mode 100644 index 0000000..80f83db --- /dev/null +++ b/place/src/main/java/com/umc/place/user/service/JwtTokenService.java @@ -0,0 +1,102 @@ +package com.umc.place.user.service; + +import com.umc.place.common.BaseException; +import com.umc.place.common.Constant; +import com.umc.place.user.dto.PostUserRes; +import com.umc.place.user.entity.User; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.Date; + +import static com.umc.place.common.BaseResponseStatus.INVALID_TOKEN; +import static com.umc.place.common.BaseResponseStatus.INVALID_USER_IDX; + +@Service +@RequiredArgsConstructor +public class JwtTokenService { + private final int accessTokenExpiryDate = 604800000; + private final int refreshTokenExpiryDate = 604800000; + + private final RedisTemplate redisTemplate; + + @Value("${auth.key}") + private String key; + + public static final String TOKEN_REGEX = "^Bearer( )*"; + public static final String TOKEN_REPLACEMENT = ""; + + private final AuthService authService; + + + //user id로 JWT 토큰 생성 + public PostUserRes createToken(User user){ + String accessToken = createAccessToken(user.getUserIdx()); + String refreshToken = createRefreshToken(user.getUserIdx()); + return new PostUserRes(accessToken, refreshToken); + } + + public String createRefreshToken(Long userIdx){ + Date now = new Date(); + String refreshToken = Jwts.builder() + .setExpiration(new Date(now.getTime() + refreshTokenExpiryDate)) + .signWith(SignatureAlgorithm.HS256, key) + .compact(); + redisTemplate.opsForValue().set(String.valueOf(userIdx), refreshToken, Duration.ofMillis(refreshTokenExpiryDate)); + return refreshToken; + } + public String createAccessToken(Long userIdx){ + Date now = new Date(); + String accessToken = Jwts.builder() + .claim("userIdx", userIdx) + .setSubject(userIdx.toString()) + .setExpiration(new Date(now.getTime() + accessTokenExpiryDate)) + .signWith(SignatureAlgorithm.HS256, key) + .compact(); + return accessToken; + } + + //토큰 재발급 시 사용 + public String validateRefreshToken(Long userIdx, String refreshTokenReq) throws BaseException { + String refreshToken = (String) redisTemplate.opsForValue().get(String.valueOf(userIdx)); + if(!refreshToken.equals(refreshTokenReq)) throw new BaseException(INVALID_TOKEN); + return refreshToken; + } + + // 회원 로그아웃 + public void logout(Long userIdx) throws BaseException { + deleteToken(userIdx); + User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + String token = user.getAccessToken(); + registerBlackList(token, Constant.LOGOUT); + } + + // 회원 탈퇴 + public void signout(Long userIdx) throws BaseException { + deleteToken(userIdx); + User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + String token = user.getAccessToken(); + registerBlackList(token, Constant.INACTIVE); + } + + // refreshToken 삭제 + public void deleteToken(Long userIdx) { + String key = String.valueOf(userIdx); + if(redisTemplate.opsForValue().get(key)!=null) redisTemplate.delete(key); + } + + // 유효한 토큰(Bearer) blacklist로 등록 + private void registerBlackList(String token, String status) { + token = token.replaceAll(TOKEN_REGEX, TOKEN_REPLACEMENT); + Date AccessTokenExpiration = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getExpiration(); + long now = (new Date()).getTime(); + + Long expiration = AccessTokenExpiration.getTime() - now; + redisTemplate.opsForValue().set(token, status, Duration.ofMillis(expiration)); + } +} diff --git a/place/src/main/java/com/umc/place/user/service/UserService.java b/place/src/main/java/com/umc/place/user/service/UserService.java index e0cf95c..bcbc3af 100644 --- a/place/src/main/java/com/umc/place/user/service/UserService.java +++ b/place/src/main/java/com/umc/place/user/service/UserService.java @@ -1,9 +1,99 @@ package com.umc.place.user.service; +import com.umc.place.common.BaseException; +import com.umc.place.user.dto.*; +import com.umc.place.user.entity.Provider; +import com.umc.place.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import static com.umc.place.common.BaseResponseStatus.*; + @Service -@RequiredArgsConstructor +@RequiredArgsConstructor //생성자 자동 생성 public class UserService { + + private final AuthService authService; + private final JwtTokenService jwtTokenService; + + private PostUserRes signUpOrLogin(Long useridx, Provider provider) throws BaseException { + //Provider provider = Provider.KAKAO; + User user = authService.findUserByIdAndProvider(useridx, provider); + + //기존 회원이 아닐 경우 회원가입 + if (user == null) + user = signup(useridx,provider); //provider 카카오로 설정 + //탈퇴한 회원일 경우 + if (user.getStatus().equals("inactive")) + throw new BaseException(ALREADY_WITHDRAW_USER); + //기존 회원이면 로그인 처리 + user.login(); //user status active로 바꾸기 + authService.saveUser(user); //userRepository에 user 저장 + return jwtTokenService.createToken(user); //user의 access token, refresh token 반환 + } + + //로그인 + public PostUserRes login(Long useridx, String provider) throws BaseException{ + try{ + if(Provider.getProviderByName(provider) == null) + throw new BaseException(INVALID_PROVIDER); + return signUpOrLogin(useridx, Provider.getProviderByName(provider)); + } catch (BaseException e){ + throw e; + } catch (Exception e){ + throw new BaseException(DATABASE_ERROR); + } + } + + //첫 로그인 시 user 생성 및 저장 (가입 연도 추가하기 createdDate) + public User signup(Long userIdx, Provider provider) { + User newuser = User.builder() + .userIdx(userIdx) + .provider(provider) + .build(); + return authService.saveUser(newuser); + } + + //회원 가입 후 사용자 정보 입력 + @Transactional(rollbackFor = Exception.class) + public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) throws BaseException { + try{ + User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); //값이 없을 경우 + String accessToken = jwtTokenService.createAccessToken(userIdx); + String refreshToken = jwtTokenService.createRefreshToken(userIdx); + + user.signup(postNewUserReq.getNickname(), postNewUserReq.getUserImg(), postNewUserReq.getBirthday(), postNewUserReq.getLocation()); + authService.saveUser(user); + + return new PostUserRes(accessToken, refreshToken); + } catch (BaseException e) { + throw e; + } catch (Exception e) { + throw new BaseException(DATABASE_ERROR); + } + } + + //닉네임 중복 확인하기 + public void checkNickname(PostNicknameReq postNicknameReq) throws BaseException { + boolean existence = authService.existsByNickname(postNicknameReq.getNickname()); + if(existence) throw new BaseException(EXIST_NICKNAME); + } + + //마이페이지 조회 + + + //사용자 프로필 수정 + + + //회원 탈퇴 + + + //로그아웃 + + + // AccessToken 재발급 + + } From a80678ea98b7f33a43ff8273c90833d85df12ac3 Mon Sep 17 00:00:00 2001 From: ri-naa Date: Wed, 2 Aug 2023 02:28:25 +0900 Subject: [PATCH 03/10] =?UTF-8?q?#13=20feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../umc/place/common/BaseResponseStatus.java | 13 ++++- .../com/umc/place/user/entity/Provider.java | 24 +++++++- .../java/com/umc/place/user/entity/User.java | 55 +++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/place/src/main/java/com/umc/place/common/BaseResponseStatus.java b/place/src/main/java/com/umc/place/common/BaseResponseStatus.java index fa94a70..7afcacb 100644 --- a/place/src/main/java/com/umc/place/common/BaseResponseStatus.java +++ b/place/src/main/java/com/umc/place/common/BaseResponseStatus.java @@ -13,12 +13,19 @@ public enum BaseResponseStatus { /** * 2000: Request 오류 */ - INVALID_STORY_IDX(false, 2100, "잘못된 스토리 Idx 입니다"), - // user(2000~2099) INVALID_USER_IDX(false, 2000, "잘못된 user Idx 입니다."), + NULL_TOKEN(false, 2001, "토큰 값을 입력해주세요."), + NULL_USER_IDX(false, 2002, "user Idx를 입력해주세요."), + NULL_PROVIDER(false, 2003, "소셜 이름을 입력해주세요."), + INVALID_PROVIDER(false, 2004, "잘못된 소셜 이름입니다."), + ALREADY_WITHDRAW_USER(false, 2005, "이미 탈퇴한 회원입니다."), + INVALID_TOKEN(false, 2006, "유효하지 않은 토큰 값입니다."), + UNSUPPORTED_TOKEN(false, 2007, "잘못된 형식의 토큰 값입니다."), + MALFORMED_TOKEN(false, 2008, "잘못된 구조의 토큰 값입니다."), // story(2100~2199) + INVALID_STORY_IDX(false, 2100, "잘못된 스토리 Idx 입니다"), // exhibition(2200~2299) INVALID_EXHIBITION_IDX(false, 2200, "잘못된 전시회 Idx 입니다."), @@ -31,6 +38,8 @@ public enum BaseResponseStatus { * 3000: Response 오류 */ // user(3000~3099) + EXPIRED_TOKEN(false, 3000, "만료된 토큰 값입니다."), + EXIST_NICKNAME(false, 3001, "이미 사용 중인 닉네임입니다."), // story(3100~3199) diff --git a/place/src/main/java/com/umc/place/user/entity/Provider.java b/place/src/main/java/com/umc/place/user/entity/Provider.java index 3d49674..2f6b807 100644 --- a/place/src/main/java/com/umc/place/user/entity/Provider.java +++ b/place/src/main/java/com/umc/place/user/entity/Provider.java @@ -1,5 +1,27 @@ package com.umc.place.user.entity; +import lombok.Getter; + +import java.util.Arrays; + +@Getter public enum Provider { - KAKAO, NAVER, GOOGLE + KAKAO(1, "카카오"), + NAVER(2, "네이버"), + GOOGLE(3, "구글"), + + ANONYMOUS(4,"비회원"); + + private int number; + private String name; + Provider(int number, String name){ + this.number = number; + this.name = name; + } + + public static Provider getProviderByName(String name){ + return Arrays.stream(Provider.values()) + .filter(r -> r.getName().equals(name)) + .findAny().orElse(null); + } } diff --git a/place/src/main/java/com/umc/place/user/entity/User.java b/place/src/main/java/com/umc/place/user/entity/User.java index 3be86ad..097c5a7 100644 --- a/place/src/main/java/com/umc/place/user/entity/User.java +++ b/place/src/main/java/com/umc/place/user/entity/User.java @@ -2,11 +2,14 @@ import com.umc.place.common.BaseEntity; import jakarta.persistence.*; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.DynamicInsert; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.util.Date; + @Entity @Getter @NoArgsConstructor @@ -21,6 +24,9 @@ public class User extends BaseEntity { @Column(nullable = false, length = 10) private String nickname; + @Column(nullable = false, length = 50) + private String email; + @Column private String userImg; @@ -31,9 +37,58 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private Provider provider; + @Column(nullable = false) + private Date birthday; + @Column(nullable = false) private String accessToken; @Column(nullable = false) private String refreshToken; + + @Builder + public User(Long userIdx, Provider provider) { + this.userIdx = userIdx; + this.provider = provider; + } + + public void signup(String nickname, String userImg, Date birthday, String location){ + this.nickname = nickname; + this.userImg = userImg; + this.birthday = birthday; + this.location = location; + } + + public void storeSignUp(String nickname, String userImg, Provider provider) { + this.nickname = nickname; + this.userImg = userImg; + this.provider = provider; + } + + //탈퇴하기 + public void signout() { + this.setNickname("알 수 없음"); + this.setProfileImg(null); + this.setProvider(Provider.ANONYMOUS); + this.setStatus("inactive"); + } + + public void setProfileImg(String userImg) { + this.userImg = userImg; + } + public void setNickname(String nickname) { + this.nickname = nickname; + } + public void setProvider(Provider provider){ + this.provider = provider; + } + + public void logout() { + this.setStatus("logout"); + } + public void login() { + this.setStatus("active"); + } + + } From bdf2fd591b0501d2035bf4ff251d59baa5f8fd0b Mon Sep 17 00:00:00 2001 From: ri-naa Date: Wed, 2 Aug 2023 14:36:36 +0900 Subject: [PATCH 04/10] =?UTF-8?q?#13=20feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/umc/place/common/Constant.java | 1 + .../place/user/repository/UserRepository.java | 10 ++++++ .../umc/place/user/service/AuthService.java | 35 +++++++++++++++++++ .../umc/place/user/service/UserService.java | 4 +-- 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 place/src/main/java/com/umc/place/user/service/AuthService.java diff --git a/place/src/main/java/com/umc/place/common/Constant.java b/place/src/main/java/com/umc/place/common/Constant.java index 2ab9eb0..fd60b3b 100644 --- a/place/src/main/java/com/umc/place/common/Constant.java +++ b/place/src/main/java/com/umc/place/common/Constant.java @@ -3,4 +3,5 @@ public class Constant { public static final String ACTIVE = "active"; public static final String INACTIVE = "inactive"; + public static final String LOGOUT = "logout"; } diff --git a/place/src/main/java/com/umc/place/user/repository/UserRepository.java b/place/src/main/java/com/umc/place/user/repository/UserRepository.java index c758e3c..f151854 100644 --- a/place/src/main/java/com/umc/place/user/repository/UserRepository.java +++ b/place/src/main/java/com/umc/place/user/repository/UserRepository.java @@ -1,9 +1,19 @@ package com.umc.place.user.repository; + +import com.umc.place.user.entity.Provider; import com.umc.place.user.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { + boolean existsByNickname(String nickname); + Optional findByUserIdxAndStatus(Long userIdx, String status); + User findByIdAndProvider(Long useridx, Provider provider); + Optional findByEmailAndProviderAndStatus(String email, Provider provider, String status); + + } diff --git a/place/src/main/java/com/umc/place/user/service/AuthService.java b/place/src/main/java/com/umc/place/user/service/AuthService.java new file mode 100644 index 0000000..d39309b --- /dev/null +++ b/place/src/main/java/com/umc/place/user/service/AuthService.java @@ -0,0 +1,35 @@ +package com.umc.place.user.service; + +import com.umc.place.user.entity.Provider; +import com.umc.place.user.entity.User; + +import com.umc.place.user.repository.UserRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class AuthService { + //카카오 구글 네이버 회원 모두 관리하기 위해 userRepository 이거로 저장 + private final UserRepository userRepository; + @Autowired + public AuthService(UserRepository userRepository) { + this.userRepository = userRepository; + } + public User saveUser(User member) { + return userRepository.save(member); + } + public User findUserByIdAndProvider(Long userIdx, Provider provider) { + return userRepository.findByIdAndProvider(userIdx, provider); + } + + public Optional findUserByIdAndStatus(Long userIdx, String status) { + return userRepository.findByUserIdxAndStatus(userIdx, status); + } + + public boolean existsByNickname(String nickname) { + return userRepository.existsByNickname(nickname); + } +} \ No newline at end of file diff --git a/place/src/main/java/com/umc/place/user/service/UserService.java b/place/src/main/java/com/umc/place/user/service/UserService.java index bcbc3af..7bdae5c 100644 --- a/place/src/main/java/com/umc/place/user/service/UserService.java +++ b/place/src/main/java/com/umc/place/user/service/UserService.java @@ -60,12 +60,12 @@ public User signup(Long userIdx, Provider provider) { @Transactional(rollbackFor = Exception.class) public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) throws BaseException { try{ - User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); //값이 없을 경우 + User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); String accessToken = jwtTokenService.createAccessToken(userIdx); String refreshToken = jwtTokenService.createRefreshToken(userIdx); user.signup(postNewUserReq.getNickname(), postNewUserReq.getUserImg(), postNewUserReq.getBirthday(), postNewUserReq.getLocation()); - authService.saveUser(user); + authService.saveUser(user); //repository에 저장 return new PostUserRes(accessToken, refreshToken); } catch (BaseException e) { From 9bff4ca9b8f7b3b8fbdaa3b5c94749d22d8c7cbb Mon Sep 17 00:00:00 2001 From: ri-naa Date: Wed, 2 Aug 2023 14:59:10 +0900 Subject: [PATCH 05/10] =?UTF-8?q?#13=20feat:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/umc/place/user/dto/PostUserReq.java | 9 +++++++++ .../com/umc/place/user/dto/PostUserRes.java | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 place/src/main/java/com/umc/place/user/dto/PostUserReq.java create mode 100644 place/src/main/java/com/umc/place/user/dto/PostUserRes.java diff --git a/place/src/main/java/com/umc/place/user/dto/PostUserReq.java b/place/src/main/java/com/umc/place/user/dto/PostUserReq.java new file mode 100644 index 0000000..3428400 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/PostUserReq.java @@ -0,0 +1,9 @@ +package com.umc.place.user.dto; + +import lombok.Getter; + +@Getter +public class PostUserReq { + private Long userIdx; + private String provider; +} diff --git a/place/src/main/java/com/umc/place/user/dto/PostUserRes.java b/place/src/main/java/com/umc/place/user/dto/PostUserRes.java new file mode 100644 index 0000000..a5f9133 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/PostUserRes.java @@ -0,0 +1,17 @@ +package com.umc.place.user.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +public class PostUserRes { + private final String accessToken; + private final String refreshToken; + + @Builder + public PostUserRes (String accessToken, String refreshToken){ + this.accessToken = accessToken; + this.refreshToken = refreshToken; + + } +} From 0764c29b78ea06e310fe961e061800fadec8b6b9 Mon Sep 17 00:00:00 2001 From: ri-naa Date: Thu, 3 Aug 2023 18:14:52 +0900 Subject: [PATCH 06/10] =?UTF-8?q?#13=20fix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8=EC=9D=B8=20dto=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/OAuth2/service/KakaoAuthService.java | 2 +- .../place/user/controller/UserController.java | 75 +++++++- .../com/umc/place/user/dto/LoginRequest.java | 11 ++ .../umc/place/user/dto/PatchProfileReq.java | 12 ++ .../umc/place/user/dto/PostNewUserReq.java | 16 ++ .../umc/place/user/dto/PostNicknameReq.java | 9 + .../com/umc/place/user/dto/PostUserReq.java | 9 - .../com/umc/place/user/dto/PostUserRes.java | 3 +- .../java/com/umc/place/user/entity/User.java | 4 +- .../umc/place/user/service/AuthService.java | 160 +++++++++++++++--- .../place/user/service/JwtTokenService.java | 102 ----------- .../place/user/service/RepositoryService.java | 35 ++++ .../umc/place/user/service/UserService.java | 25 +-- 13 files changed, 313 insertions(+), 150 deletions(-) create mode 100644 place/src/main/java/com/umc/place/user/dto/LoginRequest.java create mode 100644 place/src/main/java/com/umc/place/user/dto/PatchProfileReq.java create mode 100644 place/src/main/java/com/umc/place/user/dto/PostNewUserReq.java create mode 100644 place/src/main/java/com/umc/place/user/dto/PostNicknameReq.java delete mode 100644 place/src/main/java/com/umc/place/user/dto/PostUserReq.java delete mode 100644 place/src/main/java/com/umc/place/user/service/JwtTokenService.java create mode 100644 place/src/main/java/com/umc/place/user/service/RepositoryService.java diff --git a/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java index 9717b0a..0a2b039 100644 --- a/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java +++ b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java @@ -56,7 +56,7 @@ public KakaoTokenResponse getKakaoToken (String authorizationCode) throws JsonPr } //카카오 토큰으로 사용자 정보(Long userIdx) 가져오기 - public long getUserIdx(KakaoTokenResponse kakaoToken) throws JsonProcessingException { + public long getKakaoUserIdx (KakaoTokenResponse kakaoToken) throws JsonProcessingException { //header에 accessToken 담기 HttpHeaders headers = new HttpHeaders(); diff --git a/place/src/main/java/com/umc/place/user/controller/UserController.java b/place/src/main/java/com/umc/place/user/controller/UserController.java index ee44449..1878fdb 100644 --- a/place/src/main/java/com/umc/place/user/controller/UserController.java +++ b/place/src/main/java/com/umc/place/user/controller/UserController.java @@ -1,11 +1,80 @@ package com.umc.place.user.controller; +import com.umc.place.common.BaseException; +import com.umc.place.common.BaseResponse; +import com.umc.place.user.OAuth2.dto.KakaoTokenResponse; +import com.umc.place.user.OAuth2.service.KakaoAuthService; +import com.umc.place.user.dto.PostUserRes; +import com.umc.place.user.dto.PostNewUserReq; +import com.umc.place.user.dto.PostNicknameReq; +import com.umc.place.user.dto.LoginRequest; +import com.umc.place.user.service.AuthService; +import com.umc.place.user.service.UserService; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +import static com.umc.place.common.BaseResponseStatus.*; @RestController @RequiredArgsConstructor -@RequestMapping("/users") +@RequestMapping ("/users") public class UserController { + private final UserService userService; + private final AuthService authService; + private final KakaoAuthService kakaoAuthService; + + + //카카오 소셜 로그인 + @ResponseBody + @PostMapping("/login/kakao") + public BaseResponse login(@RequestBody LoginRequest loginRequest) throws IOException, BaseException { + try{ + KakaoTokenResponse kakaoTokenResponse = kakaoAuthService.getKakaoToken(loginRequest.getCode()); + Long useridx = kakaoAuthService.getKakaoUserIdx(kakaoTokenResponse); + PostUserRes postUserRes = userService.login(useridx, "카카오"); + return new BaseResponse<>(postUserRes); + }catch (BaseException e){ + return new BaseResponse<>(e.getStatus()); + } + } + + //회원가입 후 정보 입력 + @ResponseBody + @PostMapping("/signup") + public BaseResponse signup(@RequestBody PostNewUserReq postNewUserReq) { + try{ + return new BaseResponse<>(userService.signup_UserInfo(authService.getUserIdx(), postNewUserReq)); + } catch(BaseException e) { + return new BaseResponse<>(e.getStatus()); + } + } + + //닉네임 중복 확인 + @ResponseBody + @PostMapping("/nickname") + public BaseResponse checkNickname(@RequestBody PostNicknameReq postNicknameReq) { + try{ + userService.checkNickname(postNicknameReq); + return new BaseResponse<>(SUCCESS); + }catch (BaseException e){ + return new BaseResponse<>(e.getStatus()); + } + } + + //마이페이지 조회 + + + //회원 프로필 수정 + + + //회원 탈퇴 + + + //회원 로그아웃 + + + //AccessToken 재발급 + } diff --git a/place/src/main/java/com/umc/place/user/dto/LoginRequest.java b/place/src/main/java/com/umc/place/user/dto/LoginRequest.java new file mode 100644 index 0000000..2b8c08d --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/LoginRequest.java @@ -0,0 +1,11 @@ +package com.umc.place.user.dto; + +import lombok.Getter; +import org.antlr.v4.runtime.misc.NotNull; + +@Getter +public class LoginRequest { + @NotNull + private String code; + private String provider; +} diff --git a/place/src/main/java/com/umc/place/user/dto/PatchProfileReq.java b/place/src/main/java/com/umc/place/user/dto/PatchProfileReq.java new file mode 100644 index 0000000..7e45129 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/PatchProfileReq.java @@ -0,0 +1,12 @@ +package com.umc.place.user.dto; + +import lombok.Data; + + +@Data +public class PatchProfileReq { + private String nickname; + private String userImg; + private String email; + private String location; +} diff --git a/place/src/main/java/com/umc/place/user/dto/PostNewUserReq.java b/place/src/main/java/com/umc/place/user/dto/PostNewUserReq.java new file mode 100644 index 0000000..ef95755 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/PostNewUserReq.java @@ -0,0 +1,16 @@ +package com.umc.place.user.dto; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.util.Date; + +@Data +@RequiredArgsConstructor +public class PostNewUserReq { + private String nickname; + private String UserImg; + private String email; + private String location; + private Date birthday; +} diff --git a/place/src/main/java/com/umc/place/user/dto/PostNicknameReq.java b/place/src/main/java/com/umc/place/user/dto/PostNicknameReq.java new file mode 100644 index 0000000..c61bc66 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/dto/PostNicknameReq.java @@ -0,0 +1,9 @@ +package com.umc.place.user.dto; + +import lombok.Getter; + +@Getter +public class PostNicknameReq { + //닉네임 중복 확인 + private String nickname; +} diff --git a/place/src/main/java/com/umc/place/user/dto/PostUserReq.java b/place/src/main/java/com/umc/place/user/dto/PostUserReq.java deleted file mode 100644 index 3428400..0000000 --- a/place/src/main/java/com/umc/place/user/dto/PostUserReq.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.umc.place.user.dto; - -import lombok.Getter; - -@Getter -public class PostUserReq { - private Long userIdx; - private String provider; -} diff --git a/place/src/main/java/com/umc/place/user/dto/PostUserRes.java b/place/src/main/java/com/umc/place/user/dto/PostUserRes.java index a5f9133..0b5f2c9 100644 --- a/place/src/main/java/com/umc/place/user/dto/PostUserRes.java +++ b/place/src/main/java/com/umc/place/user/dto/PostUserRes.java @@ -9,9 +9,8 @@ public class PostUserRes { private final String refreshToken; @Builder - public PostUserRes (String accessToken, String refreshToken){ + public PostUserRes(String accessToken, String refreshToken) { this.accessToken = accessToken; this.refreshToken = refreshToken; - } } diff --git a/place/src/main/java/com/umc/place/user/entity/User.java b/place/src/main/java/com/umc/place/user/entity/User.java index 097c5a7..d023518 100644 --- a/place/src/main/java/com/umc/place/user/entity/User.java +++ b/place/src/main/java/com/umc/place/user/entity/User.java @@ -40,6 +40,7 @@ public class User extends BaseEntity { @Column(nullable = false) private Date birthday; + //삭제하기 @Column(nullable = false) private String accessToken; @@ -52,11 +53,12 @@ public User(Long userIdx, Provider provider) { this.provider = provider; } - public void signup(String nickname, String userImg, Date birthday, String location){ + public void signup(String nickname, String userImg, Date birthday, String location, String email){ this.nickname = nickname; this.userImg = userImg; this.birthday = birthday; this.location = location; + this.email = email; } public void storeSignUp(String nickname, String userImg, Provider provider) { diff --git a/place/src/main/java/com/umc/place/user/service/AuthService.java b/place/src/main/java/com/umc/place/user/service/AuthService.java index d39309b..6952045 100644 --- a/place/src/main/java/com/umc/place/user/service/AuthService.java +++ b/place/src/main/java/com/umc/place/user/service/AuthService.java @@ -1,35 +1,155 @@ package com.umc.place.user.service; -import com.umc.place.user.entity.Provider; +import com.umc.place.common.BaseException; +import com.umc.place.common.Constant; +import com.umc.place.user.dto.PostUserRes; import com.umc.place.user.entity.User; - -import com.umc.place.user.repository.UserRepository; - -import org.springframework.beans.factory.annotation.Autowired; +import io.jsonwebtoken.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.time.Duration; +import java.util.Date; -import java.util.Optional; +import static com.umc.place.common.BaseResponseStatus.*; @Service +@RequiredArgsConstructor public class AuthService { - //카카오 구글 네이버 회원 모두 관리하기 위해 userRepository 이거로 저장 - private final UserRepository userRepository; - @Autowired - public AuthService(UserRepository userRepository) { - this.userRepository = userRepository; + private final int accessTokenExpiryDate = 604800000; + private final int refreshTokenExpiryDate = 604800000; + + private final RedisTemplate redisTemplate; + + @Value("${auth.key}") + private String key; + String CLAIM_NAME = "userIdx"; + Long ADMIN_USERIDX = 0L; + String REQUEST_HEADER_NAME = "Authorization"; + public static final String TOKEN_REGEX = "^Bearer( )*"; + public static final String TOKEN_REPLACEMENT = ""; + + private final RepositoryService repositoryService; + + + /** + * userIdx 선택 + * @return 있으면 id, 없으면 ADMIN_USERIDX + */ + public Long getUserIdxOptional() throws BaseException{ + String token = getToken(); + Long userIdx; + if(token!=null) userIdx = getClaims(token).getBody().get(CLAIM_NAME, Long.class); + else userIdx = ADMIN_USERIDX; + return userIdx; + } + + /** + * userIdx 필수 + * @return 있으면 id, 없으면 EXCEPTION + */ + public Long getUserIdx() throws BaseException{ + String token = getToken(); + if(token==null) throw new BaseException(NULL_TOKEN); + return getClaims(token).getBody().get(CLAIM_NAME, Long.class); + } + + // 토큰 추출 + private String getToken() throws BaseException { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + String token = request.getHeader(REQUEST_HEADER_NAME); + if (token == null) return null; + if(redisTemplate.opsForValue().get(token)!=null) throw new BaseException(INVALID_TOKEN); + return token; } - public User saveUser(User member) { - return userRepository.save(member); + + public Jws getClaims(String token) throws BaseException{ + Jws claims = null; + token = token.replaceAll(TOKEN_REGEX, TOKEN_REPLACEMENT); + try { + claims = Jwts.parser() + .setSigningKey(key) + .parseClaimsJws(token); + } catch (ExpiredJwtException expiredJwtException) { + throw new BaseException(EXPIRED_TOKEN); + } catch (MalformedJwtException malformedJwtException) { + throw new BaseException(MALFORMED_TOKEN); + } catch (UnsupportedJwtException unsupportedJwtException) { + throw new BaseException(UNSUPPORTED_TOKEN); + } catch (Exception e) { + throw new BaseException(INVALID_TOKEN); + } + return claims; + } + + //user id로 JWT 토큰 생성 + public PostUserRes createToken(User user){ + String accessToken = createAccessToken(user.getUserIdx()); + String refreshToken = createRefreshToken(user.getUserIdx()); + return new PostUserRes(accessToken, refreshToken); + } + + public String createRefreshToken(Long userIdx){ + Date now = new Date(); + String refreshToken = Jwts.builder() + .setExpiration(new Date(now.getTime() + refreshTokenExpiryDate)) + .signWith(SignatureAlgorithm.HS256, key) + .compact(); + redisTemplate.opsForValue().set(String.valueOf(userIdx), refreshToken, Duration.ofMillis(refreshTokenExpiryDate)); + return refreshToken; + } + public String createAccessToken(Long userIdx){ + Date now = new Date(); + String accessToken = Jwts.builder() + .claim("userIdx", userIdx) + .setSubject(userIdx.toString()) + .setExpiration(new Date(now.getTime() + accessTokenExpiryDate)) + .signWith(SignatureAlgorithm.HS256, key) + .compact(); + return accessToken; } - public User findUserByIdAndProvider(Long userIdx, Provider provider) { - return userRepository.findByIdAndProvider(userIdx, provider); + + //토큰 재발급 시 사용 + public String validateRefreshToken(Long userIdx, String refreshTokenReq) throws BaseException { + String refreshToken = (String) redisTemplate.opsForValue().get(String.valueOf(userIdx)); + if(!refreshToken.equals(refreshTokenReq)) throw new BaseException(INVALID_TOKEN); + return refreshToken; + } + + // 회원 로그아웃 + public void logout(Long userIdx) throws BaseException { + deleteToken(userIdx); + User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + String token = user.getAccessToken(); + registerBlackList(token, Constant.LOGOUT); } - public Optional findUserByIdAndStatus(Long userIdx, String status) { - return userRepository.findByUserIdxAndStatus(userIdx, status); + // 회원 탈퇴 + public void signout(Long userIdx) throws BaseException { + deleteToken(userIdx); + User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + String token = user.getAccessToken(); + registerBlackList(token, Constant.INACTIVE); } - public boolean existsByNickname(String nickname) { - return userRepository.existsByNickname(nickname); + // refreshToken 삭제 + public void deleteToken(Long userIdx) { + String key = String.valueOf(userIdx); + if(redisTemplate.opsForValue().get(key)!=null) redisTemplate.delete(key); + } + + // 유효한 토큰(Bearer) blacklist로 등록 + private void registerBlackList(String token, String status) { + token = token.replaceAll(TOKEN_REGEX, TOKEN_REPLACEMENT); + Date AccessTokenExpiration = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getExpiration(); + long now = (new Date()).getTime(); + + Long expiration = AccessTokenExpiration.getTime() - now; + redisTemplate.opsForValue().set(token, status, Duration.ofMillis(expiration)); } -} \ No newline at end of file +} diff --git a/place/src/main/java/com/umc/place/user/service/JwtTokenService.java b/place/src/main/java/com/umc/place/user/service/JwtTokenService.java deleted file mode 100644 index 80f83db..0000000 --- a/place/src/main/java/com/umc/place/user/service/JwtTokenService.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.umc.place.user.service; - -import com.umc.place.common.BaseException; -import com.umc.place.common.Constant; -import com.umc.place.user.dto.PostUserRes; -import com.umc.place.user.entity.User; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.stereotype.Service; - -import java.time.Duration; -import java.util.Date; - -import static com.umc.place.common.BaseResponseStatus.INVALID_TOKEN; -import static com.umc.place.common.BaseResponseStatus.INVALID_USER_IDX; - -@Service -@RequiredArgsConstructor -public class JwtTokenService { - private final int accessTokenExpiryDate = 604800000; - private final int refreshTokenExpiryDate = 604800000; - - private final RedisTemplate redisTemplate; - - @Value("${auth.key}") - private String key; - - public static final String TOKEN_REGEX = "^Bearer( )*"; - public static final String TOKEN_REPLACEMENT = ""; - - private final AuthService authService; - - - //user id로 JWT 토큰 생성 - public PostUserRes createToken(User user){ - String accessToken = createAccessToken(user.getUserIdx()); - String refreshToken = createRefreshToken(user.getUserIdx()); - return new PostUserRes(accessToken, refreshToken); - } - - public String createRefreshToken(Long userIdx){ - Date now = new Date(); - String refreshToken = Jwts.builder() - .setExpiration(new Date(now.getTime() + refreshTokenExpiryDate)) - .signWith(SignatureAlgorithm.HS256, key) - .compact(); - redisTemplate.opsForValue().set(String.valueOf(userIdx), refreshToken, Duration.ofMillis(refreshTokenExpiryDate)); - return refreshToken; - } - public String createAccessToken(Long userIdx){ - Date now = new Date(); - String accessToken = Jwts.builder() - .claim("userIdx", userIdx) - .setSubject(userIdx.toString()) - .setExpiration(new Date(now.getTime() + accessTokenExpiryDate)) - .signWith(SignatureAlgorithm.HS256, key) - .compact(); - return accessToken; - } - - //토큰 재발급 시 사용 - public String validateRefreshToken(Long userIdx, String refreshTokenReq) throws BaseException { - String refreshToken = (String) redisTemplate.opsForValue().get(String.valueOf(userIdx)); - if(!refreshToken.equals(refreshTokenReq)) throw new BaseException(INVALID_TOKEN); - return refreshToken; - } - - // 회원 로그아웃 - public void logout(Long userIdx) throws BaseException { - deleteToken(userIdx); - User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); - String token = user.getAccessToken(); - registerBlackList(token, Constant.LOGOUT); - } - - // 회원 탈퇴 - public void signout(Long userIdx) throws BaseException { - deleteToken(userIdx); - User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); - String token = user.getAccessToken(); - registerBlackList(token, Constant.INACTIVE); - } - - // refreshToken 삭제 - public void deleteToken(Long userIdx) { - String key = String.valueOf(userIdx); - if(redisTemplate.opsForValue().get(key)!=null) redisTemplate.delete(key); - } - - // 유효한 토큰(Bearer) blacklist로 등록 - private void registerBlackList(String token, String status) { - token = token.replaceAll(TOKEN_REGEX, TOKEN_REPLACEMENT); - Date AccessTokenExpiration = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getExpiration(); - long now = (new Date()).getTime(); - - Long expiration = AccessTokenExpiration.getTime() - now; - redisTemplate.opsForValue().set(token, status, Duration.ofMillis(expiration)); - } -} diff --git a/place/src/main/java/com/umc/place/user/service/RepositoryService.java b/place/src/main/java/com/umc/place/user/service/RepositoryService.java new file mode 100644 index 0000000..cfedf55 --- /dev/null +++ b/place/src/main/java/com/umc/place/user/service/RepositoryService.java @@ -0,0 +1,35 @@ +package com.umc.place.user.service; + +import com.umc.place.user.entity.Provider; +import com.umc.place.user.entity.User; + +import com.umc.place.user.repository.UserRepository; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +public class RepositoryService { + //카카오 구글 네이버 회원 모두 관리하기 위해 userRepository에 이거로 저장 + private final UserRepository userRepository; + @Autowired + public RepositoryService(UserRepository userRepository) { + this.userRepository = userRepository; + } + public User saveUser(User member) { + return userRepository.save(member); + } + public User findUserByIdAndProvider(Long userIdx, Provider provider) { + return userRepository.findByIdAndProvider(userIdx, provider); + } + + public Optional findUserByIdAndStatus(Long userIdx, String status) { + return userRepository.findByUserIdxAndStatus(userIdx, status); + } + + public boolean existsByNickname(String nickname) { + return userRepository.existsByNickname(nickname); + } +} \ No newline at end of file diff --git a/place/src/main/java/com/umc/place/user/service/UserService.java b/place/src/main/java/com/umc/place/user/service/UserService.java index 7bdae5c..89a86ae 100644 --- a/place/src/main/java/com/umc/place/user/service/UserService.java +++ b/place/src/main/java/com/umc/place/user/service/UserService.java @@ -15,12 +15,12 @@ @RequiredArgsConstructor //생성자 자동 생성 public class UserService { + private final RepositoryService repositoryService; private final AuthService authService; - private final JwtTokenService jwtTokenService; private PostUserRes signUpOrLogin(Long useridx, Provider provider) throws BaseException { //Provider provider = Provider.KAKAO; - User user = authService.findUserByIdAndProvider(useridx, provider); + User user = repositoryService.findUserByIdAndProvider(useridx, provider); //기존 회원이 아닐 경우 회원가입 if (user == null) @@ -30,8 +30,8 @@ private PostUserRes signUpOrLogin(Long useridx, Provider provider) throws BaseEx throw new BaseException(ALREADY_WITHDRAW_USER); //기존 회원이면 로그인 처리 user.login(); //user status active로 바꾸기 - authService.saveUser(user); //userRepository에 user 저장 - return jwtTokenService.createToken(user); //user의 access token, refresh token 반환 + repositoryService.saveUser(user); //userRepository에 user 저장 + return authService.createToken(user); //user의 access token, refresh token 반환 } //로그인 @@ -39,7 +39,7 @@ public PostUserRes login(Long useridx, String provider) throws BaseException{ try{ if(Provider.getProviderByName(provider) == null) throw new BaseException(INVALID_PROVIDER); - return signUpOrLogin(useridx, Provider.getProviderByName(provider)); + return signUpOrLogin(useridx, Provider.getProviderByName(provider)); //access token, refresh token 반환 } catch (BaseException e){ throw e; } catch (Exception e){ @@ -53,19 +53,20 @@ public User signup(Long userIdx, Provider provider) { .userIdx(userIdx) .provider(provider) .build(); - return authService.saveUser(newuser); + return repositoryService.saveUser(newuser); } //회원 가입 후 사용자 정보 입력 @Transactional(rollbackFor = Exception.class) public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) throws BaseException { try{ - User user = authService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); - String accessToken = jwtTokenService.createAccessToken(userIdx); - String refreshToken = jwtTokenService.createRefreshToken(userIdx); + //userIdx로 해당 user 찾기 + User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + String accessToken = authService.createAccessToken(userIdx); + String refreshToken = authService.createRefreshToken(userIdx); - user.signup(postNewUserReq.getNickname(), postNewUserReq.getUserImg(), postNewUserReq.getBirthday(), postNewUserReq.getLocation()); - authService.saveUser(user); //repository에 저장 + user.signup(postNewUserReq.getNickname(), postNewUserReq.getUserImg(), postNewUserReq.getBirthday(), postNewUserReq.getLocation(), postNewUserReq.getEmail()); + repositoryService.saveUser(user); //repository에 저장 return new PostUserRes(accessToken, refreshToken); } catch (BaseException e) { @@ -77,7 +78,7 @@ public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) //닉네임 중복 확인하기 public void checkNickname(PostNicknameReq postNicknameReq) throws BaseException { - boolean existence = authService.existsByNickname(postNicknameReq.getNickname()); + boolean existence = repositoryService.existsByNickname(postNicknameReq.getNickname()); if(existence) throw new BaseException(EXIST_NICKNAME); } From 2501eabab9c3d488eedd8b08da9cc1933fdbf2b7 Mon Sep 17 00:00:00 2001 From: ri-naa Date: Fri, 4 Aug 2023 15:33:07 +0900 Subject: [PATCH 07/10] =?UTF-8?q?#13=20fix:=20=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8=EC=9D=B8=20dto=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/user/service/RepositoryService.java | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 place/src/main/java/com/umc/place/user/service/RepositoryService.java diff --git a/place/src/main/java/com/umc/place/user/service/RepositoryService.java b/place/src/main/java/com/umc/place/user/service/RepositoryService.java deleted file mode 100644 index cfedf55..0000000 --- a/place/src/main/java/com/umc/place/user/service/RepositoryService.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.umc.place.user.service; - -import com.umc.place.user.entity.Provider; -import com.umc.place.user.entity.User; - -import com.umc.place.user.repository.UserRepository; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Optional; - -@Service -public class RepositoryService { - //카카오 구글 네이버 회원 모두 관리하기 위해 userRepository에 이거로 저장 - private final UserRepository userRepository; - @Autowired - public RepositoryService(UserRepository userRepository) { - this.userRepository = userRepository; - } - public User saveUser(User member) { - return userRepository.save(member); - } - public User findUserByIdAndProvider(Long userIdx, Provider provider) { - return userRepository.findByIdAndProvider(userIdx, provider); - } - - public Optional findUserByIdAndStatus(Long userIdx, String status) { - return userRepository.findByUserIdxAndStatus(userIdx, status); - } - - public boolean existsByNickname(String nickname) { - return userRepository.existsByNickname(nickname); - } -} \ No newline at end of file From 80594e8cdde3a257972a8927b37aec520b27888a Mon Sep 17 00:00:00 2001 From: ri-naa Date: Fri, 4 Aug 2023 15:34:21 +0900 Subject: [PATCH 08/10] =?UTF-8?q?#13=20fix:=20UserRepository=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../place/user/repository/UserRepository.java | 2 +- .../umc/place/user/service/AuthService.java | 22 +++--------- .../umc/place/user/service/UserService.java | 34 +++++++++++-------- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/place/src/main/java/com/umc/place/user/repository/UserRepository.java b/place/src/main/java/com/umc/place/user/repository/UserRepository.java index f151854..6aa51fa 100644 --- a/place/src/main/java/com/umc/place/user/repository/UserRepository.java +++ b/place/src/main/java/com/umc/place/user/repository/UserRepository.java @@ -12,7 +12,7 @@ public interface UserRepository extends JpaRepository { boolean existsByNickname(String nickname); Optional findByUserIdxAndStatus(Long userIdx, String status); - User findByIdAndProvider(Long useridx, Provider provider); + User findByIdentifierAndProvider(String identifier, Provider provider); Optional findByEmailAndProviderAndStatus(String email, Provider provider, String status); diff --git a/place/src/main/java/com/umc/place/user/service/AuthService.java b/place/src/main/java/com/umc/place/user/service/AuthService.java index 6952045..36656ab 100644 --- a/place/src/main/java/com/umc/place/user/service/AuthService.java +++ b/place/src/main/java/com/umc/place/user/service/AuthService.java @@ -4,6 +4,7 @@ import com.umc.place.common.Constant; import com.umc.place.user.dto.PostUserRes; import com.umc.place.user.entity.User; +import com.umc.place.user.repository.UserRepository; import io.jsonwebtoken.*; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -29,25 +30,11 @@ public class AuthService { @Value("${auth.key}") private String key; String CLAIM_NAME = "userIdx"; - Long ADMIN_USERIDX = 0L; String REQUEST_HEADER_NAME = "Authorization"; public static final String TOKEN_REGEX = "^Bearer( )*"; public static final String TOKEN_REPLACEMENT = ""; - private final RepositoryService repositoryService; - - - /** - * userIdx 선택 - * @return 있으면 id, 없으면 ADMIN_USERIDX - */ - public Long getUserIdxOptional() throws BaseException{ - String token = getToken(); - Long userIdx; - if(token!=null) userIdx = getClaims(token).getBody().get(CLAIM_NAME, Long.class); - else userIdx = ADMIN_USERIDX; - return userIdx; - } + private final UserRepository userRepository; /** * userIdx 필수 @@ -124,7 +111,7 @@ public String validateRefreshToken(Long userIdx, String refreshTokenReq) throws // 회원 로그아웃 public void logout(Long userIdx) throws BaseException { deleteToken(userIdx); - User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + User user = userRepository.findByUserIdxAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); String token = user.getAccessToken(); registerBlackList(token, Constant.LOGOUT); } @@ -132,7 +119,7 @@ public void logout(Long userIdx) throws BaseException { // 회원 탈퇴 public void signout(Long userIdx) throws BaseException { deleteToken(userIdx); - User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + User user = userRepository.findByUserIdxAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); String token = user.getAccessToken(); registerBlackList(token, Constant.INACTIVE); } @@ -152,4 +139,5 @@ private void registerBlackList(String token, String status) { Long expiration = AccessTokenExpiration.getTime() - now; redisTemplate.opsForValue().set(token, status, Duration.ofMillis(expiration)); } + } diff --git a/place/src/main/java/com/umc/place/user/service/UserService.java b/place/src/main/java/com/umc/place/user/service/UserService.java index 89a86ae..3556cd9 100644 --- a/place/src/main/java/com/umc/place/user/service/UserService.java +++ b/place/src/main/java/com/umc/place/user/service/UserService.java @@ -4,42 +4,41 @@ import com.umc.place.user.dto.*; import com.umc.place.user.entity.Provider; import com.umc.place.user.entity.User; +import com.umc.place.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import static com.umc.place.common.BaseResponseStatus.*; - @Service @RequiredArgsConstructor //생성자 자동 생성 public class UserService { - private final RepositoryService repositoryService; + private final UserRepository userRepository; private final AuthService authService; - private PostUserRes signUpOrLogin(Long useridx, Provider provider) throws BaseException { + private PostUserRes signUpOrLogin(String identifier, Provider provider) throws BaseException { //Provider provider = Provider.KAKAO; - User user = repositoryService.findUserByIdAndProvider(useridx, provider); - + User user = userRepository.findByIdentifierAndProvider(identifier, provider); //기존 회원이 아닐 경우 회원가입 if (user == null) - user = signup(useridx,provider); //provider 카카오로 설정 + user = signup(identifier,provider); //provider 카카오로 설정 //탈퇴한 회원일 경우 if (user.getStatus().equals("inactive")) throw new BaseException(ALREADY_WITHDRAW_USER); //기존 회원이면 로그인 처리 user.login(); //user status active로 바꾸기 - repositoryService.saveUser(user); //userRepository에 user 저장 + userRepository.save(user); //userRepository에 user 저장 return authService.createToken(user); //user의 access token, refresh token 반환 } //로그인 - public PostUserRes login(Long useridx, String provider) throws BaseException{ + public PostUserRes login(String identifier, String provider) throws BaseException{ try{ if(Provider.getProviderByName(provider) == null) throw new BaseException(INVALID_PROVIDER); - return signUpOrLogin(useridx, Provider.getProviderByName(provider)); //access token, refresh token 반환 + return signUpOrLogin(identifier, Provider.getProviderByName(provider)); //access token, refresh token 반환 } catch (BaseException e){ throw e; } catch (Exception e){ @@ -48,12 +47,12 @@ public PostUserRes login(Long useridx, String provider) throws BaseException{ } //첫 로그인 시 user 생성 및 저장 (가입 연도 추가하기 createdDate) - public User signup(Long userIdx, Provider provider) { + public User signup(String identifier, Provider provider) { User newuser = User.builder() - .userIdx(userIdx) + .identifier(identifier) .provider(provider) .build(); - return repositoryService.saveUser(newuser); + return userRepository.save(newuser); } //회원 가입 후 사용자 정보 입력 @@ -61,12 +60,12 @@ public User signup(Long userIdx, Provider provider) { public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) throws BaseException { try{ //userIdx로 해당 user 찾기 - User user = repositoryService.findUserByIdAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); + User user = userRepository.findByUserIdxAndStatus(userIdx, "active").orElseThrow(()->new BaseException(INVALID_USER_IDX)); String accessToken = authService.createAccessToken(userIdx); String refreshToken = authService.createRefreshToken(userIdx); user.signup(postNewUserReq.getNickname(), postNewUserReq.getUserImg(), postNewUserReq.getBirthday(), postNewUserReq.getLocation(), postNewUserReq.getEmail()); - repositoryService.saveUser(user); //repository에 저장 + userRepository.save(user); //repository에 저장 return new PostUserRes(accessToken, refreshToken); } catch (BaseException e) { @@ -78,11 +77,16 @@ public PostUserRes signup_UserInfo(Long userIdx, PostNewUserReq postNewUserReq) //닉네임 중복 확인하기 public void checkNickname(PostNicknameReq postNicknameReq) throws BaseException { - boolean existence = repositoryService.existsByNickname(postNicknameReq.getNickname()); + boolean existence = userRepository.existsByNickname(postNicknameReq.getNickname()); if(existence) throw new BaseException(EXIST_NICKNAME); } //마이페이지 조회 + public GetProfileRes getProfile() throws BaseException { + Long userIdx = authService.getUserIdx(); + User user = userRepository.findByUserIdxAndStatus(userIdx, "active").orElseThrow(() -> new BaseException(INVALID_USER_IDX)); + return new GetProfileRes(user.getUserImg(), user.getNickname(), "Hello, " + user.getNickname()); + } //사용자 프로필 수정 From e0eb08860d2b721b874e165da3dceb3c729aeebf Mon Sep 17 00:00:00 2001 From: ri-naa Date: Sun, 6 Aug 2023 13:50:39 +0900 Subject: [PATCH 09/10] =?UTF-8?q?#13=20fix:=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20identifier=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/OAuth2/service/KakaoAuthService.java | 19 +++++++++---------- .../java/com/umc/place/user/entity/User.java | 15 +++++++++++++-- .../place/user/repository/UserRepository.java | 3 +-- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java index 0a2b039..dfa5931 100644 --- a/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java +++ b/place/src/main/java/com/umc/place/user/OAuth2/service/KakaoAuthService.java @@ -23,19 +23,19 @@ @Slf4j public class KakaoAuthService { - @Value("${spring.security.oauth2.client.registration.google.client-id}") + @Value("${spring.security.oauth2.client.registration.kakao.client-id}") private String kakaoClientId; - //@Value("${spring.security.oauth2.client.registration.google.client-secret}") + //@Value("${spring.security.oauth2.client.registration.kakao.client-secret}") //private String kakaoClientSecret; - @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") private String kakaoRedirectUri; - @Value("${spring.OAuth2.google.url.token}") + @Value("${spring.OAuth2.kakao.url.token}") private String KAKAO_TOKEN_REQUEST_URL; - @Value("${spring.OAuth2.google.url.profile}") + @Value("${spring.OAuth2.kakao.url.profile}") private String KAKAO_USERINFO_REQUEST_URL; //인가코드로 카카오 토큰 발급받기 @@ -55,8 +55,8 @@ public KakaoTokenResponse getKakaoToken (String authorizationCode) throws JsonPr return kakaoOAuthToken; } - //카카오 토큰으로 사용자 정보(Long userIdx) 가져오기 - public long getKakaoUserIdx (KakaoTokenResponse kakaoToken) throws JsonProcessingException { + //카카오 토큰으로 사용자 정보(식별자) 가져오기 + public String getKakaoUserIdx (KakaoTokenResponse kakaoToken) throws JsonProcessingException { //header에 accessToken 담기 HttpHeaders headers = new HttpHeaders(); @@ -71,10 +71,9 @@ public long getKakaoUserIdx (KakaoTokenResponse kakaoToken) throws JsonProcessin JsonNode jsonNode = objectMapper.readTree(response.getBody()); //user id 가져오기 - String id = jsonNode.get("id").asText(); - long userIdx = Long.parseLong(id); + String identifier = jsonNode.get("id").asText(); - return userIdx; + return identifier; } } diff --git a/place/src/main/java/com/umc/place/user/entity/User.java b/place/src/main/java/com/umc/place/user/entity/User.java index d023518..05e1c2f 100644 --- a/place/src/main/java/com/umc/place/user/entity/User.java +++ b/place/src/main/java/com/umc/place/user/entity/User.java @@ -21,6 +21,9 @@ public class User extends BaseEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userIdx; + @Column(nullable = false) + private String identifier; + @Column(nullable = false, length = 10) private String nickname; @@ -48,8 +51,8 @@ public class User extends BaseEntity { private String refreshToken; @Builder - public User(Long userIdx, Provider provider) { - this.userIdx = userIdx; + public User(String identifier, Provider provider) { + this.identifier = identifier; this.provider = provider; } @@ -85,6 +88,14 @@ public void setProvider(Provider provider){ this.provider = provider; } + public void modifyNickname(String nickname) { + this.nickname = nickname; + } + + public void modifyUserImg(String userImg) { + this.userImg = userImg; + } + public void logout() { this.setStatus("logout"); } diff --git a/place/src/main/java/com/umc/place/user/repository/UserRepository.java b/place/src/main/java/com/umc/place/user/repository/UserRepository.java index 6aa51fa..f2e84b2 100644 --- a/place/src/main/java/com/umc/place/user/repository/UserRepository.java +++ b/place/src/main/java/com/umc/place/user/repository/UserRepository.java @@ -13,7 +13,6 @@ public interface UserRepository extends JpaRepository { boolean existsByNickname(String nickname); Optional findByUserIdxAndStatus(Long userIdx, String status); User findByIdentifierAndProvider(String identifier, Provider provider); - Optional findByEmailAndProviderAndStatus(String email, Provider provider, String status); - + Optional findByIdentifierAndProviderAndStatus(String identifier, Provider provider, String status); } From 9a7f146ce45699dfdcb84f9c31efc31e1f2c92dc Mon Sep 17 00:00:00 2001 From: lsn5963 <77337977+lsn5963@users.noreply.github.com> Date: Wed, 9 Aug 2023 18:51:39 +0900 Subject: [PATCH 10/10] =?UTF-8?q?#18=20feat:=20S3=20API=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- place/build.gradle | 1 + .../umc/place/common/config/AWSConfig.java | 32 ++++++++ .../controller/FileUploadController.java | 35 ++++++++ .../umc/place/common/service/S3Upload.java | 79 +++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 place/src/main/java/com/umc/place/common/config/AWSConfig.java create mode 100644 place/src/main/java/com/umc/place/common/controller/FileUploadController.java create mode 100644 place/src/main/java/com/umc/place/common/service/S3Upload.java diff --git a/place/build.gradle b/place/build.gradle index 984cb62..be629fd 100644 --- a/place/build.gradle +++ b/place/build.gradle @@ -25,6 +25,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/place/src/main/java/com/umc/place/common/config/AWSConfig.java b/place/src/main/java/com/umc/place/common/config/AWSConfig.java new file mode 100644 index 0000000..856b210 --- /dev/null +++ b/place/src/main/java/com/umc/place/common/config/AWSConfig.java @@ -0,0 +1,32 @@ +package com.umc.place.common.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +@Configuration +public class AWSConfig { + + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials basicAWSCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(basicAWSCredentials)) + .build(); + } +} diff --git a/place/src/main/java/com/umc/place/common/controller/FileUploadController.java b/place/src/main/java/com/umc/place/common/controller/FileUploadController.java new file mode 100644 index 0000000..f61723c --- /dev/null +++ b/place/src/main/java/com/umc/place/common/controller/FileUploadController.java @@ -0,0 +1,35 @@ +package com.umc.place.common.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import com.umc.place.common.service.S3Upload; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; + +@RequiredArgsConstructor +@RestController +public class FileUploadController { + + private final S3Upload s3Upload; + + @PostMapping("/upload") + public ResponseEntity uploadFile(MultipartFile[] multipartFileLis) throws IOException { + return ResponseEntity.ok( + s3Upload.upload(multipartFileLis) + ); + } +} \ No newline at end of file diff --git a/place/src/main/java/com/umc/place/common/service/S3Upload.java b/place/src/main/java/com/umc/place/common/service/S3Upload.java new file mode 100644 index 0000000..4c0586d --- /dev/null +++ b/place/src/main/java/com/umc/place/common/service/S3Upload.java @@ -0,0 +1,79 @@ +package com.umc.place.common.service; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +@Service +public class S3Upload { + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private final AmazonS3Client amazonS3Client; + ; + + //https://jforj.tistory.com/261 + public List upload(MultipartFile[] multipartFileList) throws IOException { + List imagePathList = new ArrayList<>(); + + for (MultipartFile multipartFile : multipartFileList) { + String originalName = multipartFile.getOriginalFilename(); // 파일 이름 + long size = multipartFile.getSize(); // 파일 크기 + + ObjectMetadata objectMetaData = new ObjectMetadata(); + objectMetaData.setContentType(multipartFile.getContentType()); + objectMetaData.setContentLength(size); + + // S3에 업로드 + amazonS3Client.putObject( + new PutObjectRequest(bucket, originalName, multipartFile.getInputStream(), objectMetaData) + .withCannedAcl(CannedAccessControlList.PublicRead) + ); + + String imagePath = amazonS3Client.getUrl(bucket, originalName).toString(); // 접근가능한 URL 가져오기 + imagePathList.add(imagePath); + } + return imagePathList; + } +} + +// String s3FileName = UUID.randomUUID() + "-" + multipartFile.getOriginalFilename(); +// +// ObjectMetadata objMeta = new ObjectMetadata(); +// objMeta.setContentLength(multipartFile.getInputStream().available()); +// +// amazonS3.putObject(bucket, s3FileName, multipartFile.getInputStream(), objMeta); +// +// return amazonS3.getUrl(bucket, s3FileName).toString(); +// for(MultipartFile multipartFile: multipartFileList) { +// String originalName = multipartFile.getOriginalFilename(); // 파일 이름 +// long size = multipartFile.getSize(); // 파일 크기 +// +// ObjectMetadata objectMetaData = new ObjectMetadata(); +// objectMetaData.setContentType(multipartFile.getContentType()); +// objectMetaData.setContentLength(size); +// +// // S3에 업로드 +// amazonS3Client.putObject( +// new PutObjectRequest(S3Bucket, originalName, multipartFile.getInputStream(), objectMetaData) +// .withCannedAcl(CannedAccessControlList.PublicRead) +// ); +// +// String imagePath = amazonS3Client.getUrl(S3Bucket, originalName).toString(); // 접근가능한 URL 가져오기 +// imagePathList.add(imagePath); +// } +// +// return new ResponseEntity(imagePathList, HttpStatus.OK);