Skip to content

Commit

Permalink
Merge pull request #507 from Travel-in-nanaland/feat/#495-memberProfile
Browse files Browse the repository at this point in the history
[Feat] 회원 프로필 수정 API에서 MultipartFile 제거
  • Loading branch information
jyajoo authored Nov 14, 2024
2 parents 457965e + 16b98c0 commit 96e79ef
Show file tree
Hide file tree
Showing 17 changed files with 489 additions and 103 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.jeju.nanaland.domain.common.entity;

import com.jeju.nanaland.global.image_upload.dto.S3ImageDto;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.AccessLevel;
Expand All @@ -25,8 +24,8 @@ public ImageFile(String thumbnailUrl, String originUrl) {
this.originUrl = originUrl;
}

public void updateImageFile(S3ImageDto s3ImageDto) {
this.originUrl = s3ImageDto.getOriginUrl();
this.thumbnailUrl = s3ImageDto.getThumbnailUrl();
public void updateImageFile(String originUrl, String thumbnailUrl) {
this.originUrl = originUrl;
this.thumbnailUrl = thumbnailUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
import com.jeju.nanaland.domain.common.dto.ImageFileDto;
import com.jeju.nanaland.domain.common.entity.ImageFile;
import com.jeju.nanaland.domain.common.repository.ImageFileRepository;
import com.jeju.nanaland.domain.member.service.MemberProfileService;
import com.jeju.nanaland.domain.member.service.ProfileImageService;
import com.jeju.nanaland.global.exception.ServerErrorException;
import com.jeju.nanaland.global.image_upload.S3ImageService;
import com.jeju.nanaland.global.image_upload.dto.S3ImageDto;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import lombok.RequiredArgsConstructor;
Expand All @@ -30,16 +28,13 @@
@RequiredArgsConstructor
public class ImageFileService {

private final MemberProfileService memberProfileService;
private final ProfileImageService profileImageService;
private final FileService fileService;
@Value("${cloud.aws.s3.memberProfileDirectory}")
private String MEMBER_PROFILE_DIRECTORY;
private final S3ImageService s3ImageService;
private final ImageFileRepository imageFileRepository;

private final List<String> defaultProfile = Arrays.asList("LightPurple.png", "LightGray.png",
"Gray.png", "DeepBlue.png");
private final Random random = new Random();

public ImageFile saveS3ImageFile(S3ImageDto s3ImageDto) {
ImageFile imageFile = ImageFile.builder()
Expand All @@ -63,12 +58,6 @@ public ImageFile uploadAndSaveImageFile(File file, boolean autoThumbnail,
}
}

public ImageFile getRandomProfileImageFile() {
String selectedProfile = defaultProfile.get(random.nextInt(defaultProfile.size()));
S3ImageDto s3ImageDto = s3ImageService.getS3Urls(selectedProfile, MEMBER_PROFILE_DIRECTORY);
return saveS3ImageFile(s3ImageDto);
}

public List<ImageFileDto> getPostImageFilesByPostIdIncludeFirstImage(Long postId,
ImageFileDto firstImage) {
List<ImageFileDto> images = new ArrayList<>();
Expand All @@ -87,7 +76,7 @@ public void uploadMemberProfileImage(Long memberId, File file) {
MultipartFile multipartFile = fileService.convertFileToMultipartFile(file);
s3ImageService.uploadImageToS3(multipartFile, true, MEMBER_PROFILE_DIRECTORY)
.thenAccept(s3ImageDto ->
memberProfileService.updateMemberProfileImage(memberId, s3ImageDto)
profileImageService.updateMemberProfileImage(memberId, s3ImageDto)
)
.exceptionally(e -> {
log.error("파일 업로드 오류: {}", e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,12 @@ public BaseResponse<Null> withdrawal(
@ApiResponse(responseCode = "401", description = "accessToken이 유효하지 않은 경우", content = @Content),
@ApiResponse(responseCode = "500", description = "이미지 업로드에 실패한 경우", content = @Content)
})
@PatchMapping(
value = "/profile",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@PatchMapping(value = "/profile")
public BaseResponse<String> updateProfile(
@AuthMember MemberInfoDto memberInfoDto,
@RequestPart @Valid MemberRequest.ProfileUpdateDto reqDto,
@RequestPart(required = false) MultipartFile multipartFile) {

memberProfileService.updateProfile(memberInfoDto, reqDto, multipartFile);
@RequestBody @Valid MemberRequest.ProfileUpdateDto reqDto,
@RequestParam(required = false) String fileKey) {
memberProfileService.updateProfile(memberInfoDto, reqDto, fileKey);
return BaseResponse.success(UPDATE_MEMBER_PROFILE_SUCCESS);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -51,8 +50,7 @@ public class MemberLoginService {
private final ImageFileService imageFileService;
private final FcmTokenService fcmTokenService;
private final FileService fileService;
@Value("${cloud.aws.s3.memberProfileDirectory}")
private String MEMBER_PROFILE_DIRECTORY;
private final MemberProfileService memberProfileService;

/**
* 회원 가입
Expand All @@ -75,7 +73,7 @@ public JwtDto join(MemberRequest.JoinDto joinDto, MultipartFile multipartFile) {

String nickname = determineNickname(joinDto);
validateNickname(nickname);
ImageFile profileImageFile = imageFileService.getRandomProfileImageFile();
ImageFile profileImageFile = memberProfileService.saveRandomProfileImageFile();
Member member = createMember(joinDto, profileImageFile, nickname);

// GUEST가 아닌 경우, 이용약관 저장
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import static com.jeju.nanaland.global.exception.ErrorCode.MEMBER_NOT_FOUND;
import static com.jeju.nanaland.global.exception.ErrorCode.NICKNAME_DUPLICATE;
import static com.jeju.nanaland.global.exception.ErrorCode.SERVER_ERROR;

import com.jeju.nanaland.domain.common.data.Language;
import com.jeju.nanaland.domain.common.dto.ImageFileDto;
import com.jeju.nanaland.domain.common.entity.ImageFile;
import com.jeju.nanaland.domain.common.repository.ImageFileRepository;
import com.jeju.nanaland.domain.member.dto.MemberRequest;
import com.jeju.nanaland.domain.member.dto.MemberResponse;
import com.jeju.nanaland.domain.member.dto.MemberResponse.MemberInfoDto;
Expand All @@ -17,85 +17,49 @@
import com.jeju.nanaland.global.exception.ErrorCode;
import com.jeju.nanaland.global.exception.NotFoundException;
import com.jeju.nanaland.global.exception.ServerErrorException;
import com.jeju.nanaland.global.image_upload.S3ImageService;
import com.jeju.nanaland.global.file.service.FileUploadService;
import com.jeju.nanaland.global.image_upload.dto.S3ImageDto;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
@Slf4j
public class MemberProfileService {

private final S3ImageService s3ImageService;
private final MemberRepository memberRepository;
@Value("${cloud.aws.s3.memberProfileDirectory}")
private String MEMBER_PROFILE_DIRECTORY;
private final ImageFileRepository imageFileRepository;
private final FileUploadService fileUploadService;
private final List<String> defaultProfile = Arrays.asList("default/LightPurple.png", "default/LightGray.png",
"default/Gray.png", "default/DeepBlue.png");
private final Random random = new Random();

/**
* 유저 프로필 수정
*
* @param memberInfoDto 회원 정보
* @param profileUpdateDto 프로필 수정 정보
* @param multipartFile 프로필 사진
* @throws ServerErrorException 사진 업로드가 실패했을 경우
*/
@Transactional
public void updateProfile(MemberInfoDto memberInfoDto,
MemberRequest.ProfileUpdateDto profileUpdateDto, MultipartFile multipartFile) {
public void updateProfile(MemberInfoDto memberInfoDto, MemberRequest.ProfileUpdateDto profileUpdateDto, String fileKey) {

Member member = memberInfoDto.getMember();
validateNickname(profileUpdateDto.getNickname(), member);
if (multipartFile != null) {
updateProfileImage(multipartFile, member);
if (fileKey != null) {
S3ImageDto s3ImageDto = fileUploadService.getCloudImageUrls(fileKey);
member.getProfileImageFile().updateImageFile(s3ImageDto.getOriginUrl(), s3ImageDto.getThumbnailUrl());
}
member.updateProfile(profileUpdateDto);
}

/**
* 프로필 사진 업데이트
*
* @param multipartFile 프로필 사진
* @param member 회원
*/
private void updateProfileImage(MultipartFile multipartFile, Member member) {
ImageFile profileImageFile = member.getProfileImageFile();
try {
CompletableFuture<S3ImageDto> futureS3ImageDto = s3ImageService.uploadImageToS3(multipartFile,
true,
MEMBER_PROFILE_DIRECTORY);
S3ImageDto s3ImageDto = futureS3ImageDto.get();
if (!s3ImageService.isDefaultProfileImage(profileImageFile)) {
s3ImageService.deleteImageS3(profileImageFile, MEMBER_PROFILE_DIRECTORY);
}
profileImageFile.updateImageFile(s3ImageDto);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("스레드 중단: {}", e.getMessage());
throw new ServerErrorException(SERVER_ERROR.getMessage());
} catch (ExecutionException e) {
log.error("비동기 작업 실행 중 발생하는 예외 발생 : {}", e.getMessage());
throw new ServerErrorException(SERVER_ERROR.getMessage());
}
}

@Transactional
public void updateMemberProfileImage(Long memberId, S3ImageDto s3ImageDto) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND.getMessage()));
ImageFile originImageFile = member.getProfileImageFile();
originImageFile.updateImageFile(s3ImageDto);
}

/**
* 닉네임 중복 확인
*
Expand Down Expand Up @@ -196,4 +160,18 @@ public void validateNickname(String nickname, Long memberId) {
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND.getMessage()));
validateNickname(nickname, member);
}

public ImageFile saveRandomProfileImageFile() {
S3ImageDto s3ImageDto = getRandomImageFile();
ImageFile imageFile = ImageFile.builder()
.originUrl(s3ImageDto.getOriginUrl())
.thumbnailUrl(s3ImageDto.getThumbnailUrl())
.build();
return imageFileRepository.save(imageFile);
}

public S3ImageDto getRandomImageFile() {
String selectedProfile = defaultProfile.get(random.nextInt(defaultProfile.size()));
return fileUploadService.getCloudImageUrls(selectedProfile);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.jeju.nanaland.domain.member.service;

import static com.jeju.nanaland.global.exception.ErrorCode.MEMBER_NOT_FOUND;

import com.jeju.nanaland.domain.common.entity.ImageFile;
import com.jeju.nanaland.domain.member.entity.Member;
import com.jeju.nanaland.domain.member.repository.MemberRepository;
import com.jeju.nanaland.global.exception.NotFoundException;
import com.jeju.nanaland.global.image_upload.dto.S3ImageDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Slf4j
public class ProfileImageService {

private final MemberRepository memberRepository;

@Transactional
public void updateMemberProfileImage(Long memberId, S3ImageDto s3ImageDto) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException(MEMBER_NOT_FOUND.getMessage()));
ImageFile originImageFile = member.getProfileImageFile();
originImageFile.updateImageFile(s3ImageDto.getOriginUrl(), s3ImageDto.getThumbnailUrl());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.jeju.nanaland.global.image_upload;
package com.jeju.nanaland.global.config;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class S3Config {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ public enum ErrorCode {
SELF_REPORT_NOT_ALLOWED(BAD_REQUEST, "본인을 신고하는 요청은 유효하지 않습니다."),
ALREADY_REPORTED(BAD_REQUEST, "이미 신고되었습니다."),
NO_NOTIFICATION_CONSENT(BAD_REQUEST, "알림 동의를 하지 않은 유저입니다."),
INVALID_FILE_SIZE(BAD_REQUEST, "파일 크기가 유효하지 않습니다."),
INVALID_FILE_EXTENSION_TYPE(BAD_REQUEST, "해당 카테고리에서 지원하지 않는 파일 형식입니다."),
NO_FILE_EXTENSION(BAD_REQUEST, "파일 확장자가 없습니다."),

//INTERNAL_SERVER_ERROR
SERVER_ERROR(INTERNAL_SERVER_ERROR, "서버측 에러입니다."),
EXTRACT_NAME_ERROR(INTERNAL_SERVER_ERROR, "이미지 파일 이름 추출 에러"),
MAIL_FAIL_ERROR(INTERNAL_SERVER_ERROR, "메일 전송 실패"),
FILE_FAIL_ERROR(INTERNAL_SERVER_ERROR, "파일 변환 중 오류가 발생했습니다."),
FILE_UPLOAD_FAIL(INTERNAL_SERVER_ERROR, "파일 업로드 실패"),

//UNAUTHORIZED
UNAUTHORIZED_USER(UNAUTHORIZED, "access token이 존재하지 않습니다."),
Expand All @@ -62,6 +66,7 @@ public enum ErrorCode {
KEYWORD_NOT_FOUND(NOT_FOUND, "존재하지 않는 키워드 입니다."),
INFO_TYPE_NOT_FOUND(NOT_FOUND, "존재하지 않는 InfoType 입니다."),
LANGUAGE_NOT_FOUND(NOT_FOUND, "지원하지 않는 언어입니다."),
FILE_S3_NOT_FOUNE(NOT_FOUND, "파일을 S3에서 찾을 수 없습니다."),

// CONFLICT
CONFLICT_DATA(CONFLICT, "이미 존재하는 데이터입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ public enum SuccessCode {

// notification
NOTIFICATION_LIST_SUCCESS(OK, "알림 조회 성공"),
SEND_NOTIFICATION_SUCCESS(CREATED, "알림 전송 성공");
SEND_NOTIFICATION_SUCCESS(CREATED, "알림 전송 성공"),

// file
GET_PRESIGNED_URL_SUCCESS(OK, "Pre-Signed URL 발급 성공"),
COMPLETE_PRESIGNED_URL_SUCCESS(OK, "Pre-Signed URL 업로드 완료 성공");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.jeju.nanaland.global.file.controller;

import static com.jeju.nanaland.global.exception.SuccessCode.COMPLETE_PRESIGNED_URL_SUCCESS;
import static com.jeju.nanaland.global.exception.SuccessCode.GET_PRESIGNED_URL_SUCCESS;

import com.jeju.nanaland.global.BaseResponse;
import com.jeju.nanaland.global.file.dto.FileRequest;
import com.jeju.nanaland.global.file.dto.FileResponse;
import com.jeju.nanaland.global.file.service.FileUploadService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
@Slf4j
@Tag(name = "파일(File)", description = "파일(File) API입니다.")
public class FileUploadController {

private final FileUploadService fileUploadService;

@Operation(summary = "Pre-Signed URL 업로드 시작 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "400", description = "올바르지 않은 요청인 경우", content = @Content),
@ApiResponse(responseCode = "500", description = "서버측 에러", content = @Content)
})
@PostMapping("/upload-init")
public BaseResponse<FileResponse.InitResultDto> uploadInit(
@RequestBody @Valid FileRequest.InitCommandDto initCommandDto
){
FileResponse.InitResultDto initResultDto = fileUploadService.uploadInit(initCommandDto);
return BaseResponse.success(GET_PRESIGNED_URL_SUCCESS, initResultDto);
}

@Operation(summary = "Pre-Signed URL 업로드 완료 API")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "성공"),
@ApiResponse(responseCode = "500", description = "서버측 에러", content = @Content)
})
@PostMapping("/upload-complete")
public BaseResponse<Void> uploadComplete(
@RequestBody @Valid FileRequest.CompleteCommandDto completeCommandDto
) {
fileUploadService.uploadComplete(completeCommandDto);
return BaseResponse.success(COMPLETE_PRESIGNED_URL_SUCCESS);
}
}
Loading

0 comments on commit 96e79ef

Please sign in to comment.