From df74765516b5cecef510222a123fff502814add8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:49:28 +0900 Subject: [PATCH 01/12] =?UTF-8?q?:art:=20CHORE.=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=82=B4=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/FairytaleCommandServiceImpl.java | 4 ++++ .../tbd/domain/user/enums/Gender.java | 3 +++ .../fairytale/tbd/domain/user/enums/Role.java | 3 +++ .../user/service/UserCommandServiceImpl.java | 4 ++++ .../user/service/UserQueryServiceImpl.java | 6 +++++ .../web/controller/UserRestController.java | 3 +++ .../web/controller/VoiceRestController.java | 23 +++++++++++++++++++ 7 files changed, 46 insertions(+) diff --git a/src/main/java/fairytale/tbd/domain/fairytale/service/FairytaleCommandServiceImpl.java b/src/main/java/fairytale/tbd/domain/fairytale/service/FairytaleCommandServiceImpl.java index 942060a..f00f524 100644 --- a/src/main/java/fairytale/tbd/domain/fairytale/service/FairytaleCommandServiceImpl.java +++ b/src/main/java/fairytale/tbd/domain/fairytale/service/FairytaleCommandServiceImpl.java @@ -17,9 +17,13 @@ public class FairytaleCommandServiceImpl implements FairytaleCommandService { private final FairytaleRepository fairytaleRepository; + /** + * 동화 추가 서비스 로직 + */ @Override @Transactional public Fairytale saveFairytale(FairytaleRequestDTO.AddFairytaleRequestDTO request) { + // TODO BEAN VALIDATION if (fairytaleRepository.existsByName(request.getName())) { throw new GeneralException(ErrorStatus._FAIRYTALE_EXIST_ERROR); } diff --git a/src/main/java/fairytale/tbd/domain/user/enums/Gender.java b/src/main/java/fairytale/tbd/domain/user/enums/Gender.java index b9c8f4b..939e9c2 100644 --- a/src/main/java/fairytale/tbd/domain/user/enums/Gender.java +++ b/src/main/java/fairytale/tbd/domain/user/enums/Gender.java @@ -1,5 +1,8 @@ package fairytale.tbd.domain.user.enums; +/** + * 사용자 성별 + */ public enum Gender { MALE, FEMALE; } diff --git a/src/main/java/fairytale/tbd/domain/user/enums/Role.java b/src/main/java/fairytale/tbd/domain/user/enums/Role.java index beb6aaa..7f531d0 100644 --- a/src/main/java/fairytale/tbd/domain/user/enums/Role.java +++ b/src/main/java/fairytale/tbd/domain/user/enums/Role.java @@ -1,5 +1,8 @@ package fairytale.tbd.domain.user.enums; +/** + * 권한 역할 + */ public enum Role { ROLE_USER, ROLE_ADMIN, ROLE_GUEST } diff --git a/src/main/java/fairytale/tbd/domain/user/service/UserCommandServiceImpl.java b/src/main/java/fairytale/tbd/domain/user/service/UserCommandServiceImpl.java index 6d65ca1..61aa50b 100644 --- a/src/main/java/fairytale/tbd/domain/user/service/UserCommandServiceImpl.java +++ b/src/main/java/fairytale/tbd/domain/user/service/UserCommandServiceImpl.java @@ -20,6 +20,10 @@ public class UserCommandServiceImpl implements UserCommandService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + /** + * 사용자가 회원가입에서 입력한 PLANE TEXT를 + * 암호화 하고, 유저의 권한을 추가 한 후, 데이터베이스에 저장 + */ @Transactional @Override public User addUser(UserRequestDTO.AddUserDTO request) { diff --git a/src/main/java/fairytale/tbd/domain/user/service/UserQueryServiceImpl.java b/src/main/java/fairytale/tbd/domain/user/service/UserQueryServiceImpl.java index e1a3762..d5050b8 100644 --- a/src/main/java/fairytale/tbd/domain/user/service/UserQueryServiceImpl.java +++ b/src/main/java/fairytale/tbd/domain/user/service/UserQueryServiceImpl.java @@ -16,6 +16,9 @@ public class UserQueryServiceImpl implements UserQueryService { private final UserRepository userRepository; + /** + * 사용자를 권한과 함께 반환 + */ @Override public Optional getUserWithAuthorities(String loginId) { User user = userRepository.findByLoginId(loginId).orElse(null); @@ -23,6 +26,9 @@ public Optional getUserWithAuthorities(String loginId) { return Optional.ofNullable(user); } + /** + * 사용자의 RefreshToken을 데이터베이스에 업데이트 + */ @Transactional @Override public void updateRefreshToken(User user, String reIssuedRefreshToken) { diff --git a/src/main/java/fairytale/tbd/domain/user/web/controller/UserRestController.java b/src/main/java/fairytale/tbd/domain/user/web/controller/UserRestController.java index 225fcb8..9a185cf 100644 --- a/src/main/java/fairytale/tbd/domain/user/web/controller/UserRestController.java +++ b/src/main/java/fairytale/tbd/domain/user/web/controller/UserRestController.java @@ -24,6 +24,9 @@ public class UserRestController { private final UserCommandService userCommandService; private static final Logger LOGGER = LogManager.getLogger(UserRestController.class); + /** + * 회원 가입 + */ @PostMapping("/signup") public ApiResponse join(@Valid @RequestBody UserRequestDTO.AddUserDTO request) { LOGGER.info("request = {}", request); diff --git a/src/main/java/fairytale/tbd/domain/voice/web/controller/VoiceRestController.java b/src/main/java/fairytale/tbd/domain/voice/web/controller/VoiceRestController.java index d1d32a3..3c27d32 100644 --- a/src/main/java/fairytale/tbd/domain/voice/web/controller/VoiceRestController.java +++ b/src/main/java/fairytale/tbd/domain/voice/web/controller/VoiceRestController.java @@ -1,17 +1,23 @@ package fairytale.tbd.domain.voice.web.controller; +import java.util.List; +import java.util.Map; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; import fairytale.tbd.domain.user.entity.User; import fairytale.tbd.domain.voice.converter.VoiceConverter; import fairytale.tbd.domain.voice.entity.Voice; import fairytale.tbd.domain.voice.service.VoiceCommandService; +import fairytale.tbd.domain.voice.service.VoiceQueryService; import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO; import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO; import fairytale.tbd.global.annotation.LoginUser; @@ -26,7 +32,11 @@ public class VoiceRestController { private static final Logger LOGGER = LogManager.getLogger(VoiceRestController.class); private final VoiceCommandService voiceCommandService; + private final VoiceQueryService voiceQueryService; + /** + * 사용자의 음성 추가 + */ @PostMapping("") public ApiResponse addVoice( @Valid @ModelAttribute VoiceRequestDTO.AddVoiceDTO request, @LoginUser User user) { @@ -35,14 +45,27 @@ public ApiResponse addVoice( return ApiResponse.onSuccess(VoiceConverter.toAddVoiceResult(voice)); } + /** + * 동화의 문장 추가 + */ @PostMapping("/segment") public ApiResponse addSegment( @Valid @RequestBody VoiceRequestDTO.AddSegmentDTO request) { LOGGER.info("::: Segment 추가 요청 :::"); + LOGGER.info("request = {}", request); VoiceResponseDTO.AddTTSSegmentResultDTO result = voiceCommandService.addTTSSegment(request); LOGGER.info("::: Segment 추가 성공 SegmentId = {}:::", result.getSegmentId()); return ApiResponse.onSuccess(result); } + @GetMapping("/segment/test") + public ApiResponse>> getUserSegment( + @LoginUser User user, + @RequestParam(name = "fairytaleName") String fairytaleName) { + LOGGER.info("getUserSegment START"); + Map> userTTSSegmentList = voiceQueryService.getUserTTSSegmentList( + user, fairytaleName); + return ApiResponse.onSuccess(userTTSSegmentList); + } } From eeeca2185c2d359ca1d258506da65068918ec2ca Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:49:50 +0900 Subject: [PATCH 02/12] =?UTF-8?q?:art:=20CHORE.=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=82=B4=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fairytale/web/controller/FairytaleRestController.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/fairytale/tbd/domain/fairytale/web/controller/FairytaleRestController.java b/src/main/java/fairytale/tbd/domain/fairytale/web/controller/FairytaleRestController.java index 5a5269b..f9285b6 100644 --- a/src/main/java/fairytale/tbd/domain/fairytale/web/controller/FairytaleRestController.java +++ b/src/main/java/fairytale/tbd/domain/fairytale/web/controller/FairytaleRestController.java @@ -20,6 +20,9 @@ public class FairytaleRestController { private final FairytaleCommandService fairytaleCommandService; + /** + * 동화 추가 메서드 + */ @PostMapping("") public ApiResponse addFairytale(@Valid @RequestBody FairytaleRequestDTO.AddFairytaleRequestDTO request) { From 3935439606ca1ee4f7354b0a0ea81e92d1cd16b0 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:51:07 +0900 Subject: [PATCH 03/12] =?UTF-8?q?:sparkles:=20FEAT.=20FileConverter.java?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EB=B3=80=ED=99=98=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tbd/global/util/FileConvertException.java | 10 ++++++++ .../tbd/global/util/FileConverter.java | 23 ++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/main/java/fairytale/tbd/global/util/FileConvertException.java diff --git a/src/main/java/fairytale/tbd/global/util/FileConvertException.java b/src/main/java/fairytale/tbd/global/util/FileConvertException.java new file mode 100644 index 0000000..ae1f603 --- /dev/null +++ b/src/main/java/fairytale/tbd/global/util/FileConvertException.java @@ -0,0 +1,10 @@ +package fairytale.tbd.global.util; + +import fairytale.tbd.global.enums.statuscode.BaseCode; +import fairytale.tbd.global.exception.GeneralException; + +public class FileConvertException extends GeneralException { + public FileConvertException(BaseCode errorStatus) { + super(errorStatus); + } +} diff --git a/src/main/java/fairytale/tbd/global/util/FileConverter.java b/src/main/java/fairytale/tbd/global/util/FileConverter.java index 57221dd..b063487 100644 --- a/src/main/java/fairytale/tbd/global/util/FileConverter.java +++ b/src/main/java/fairytale/tbd/global/util/FileConverter.java @@ -4,16 +4,27 @@ import java.io.FileInputStream; import java.io.IOException; +import org.apache.logging.log4j.LogManager; import org.springframework.mock.web.MockMultipartFile; import org.springframework.web.multipart.MultipartFile; +import fairytale.tbd.global.enums.statuscode.ErrorStatus; +import fairytale.tbd.global.exception.GeneralException; + public class FileConverter { - public static MultipartFile toMultipartFile(File file, String fileName) throws IOException { - FileInputStream input = new FileInputStream(file); - MultipartFile multipartFile = new MockMultipartFile("file", - file.getName(), - "audio/mpeg", - input); + public static MultipartFile toMultipartFile(File file, String fileName) { + + MultipartFile multipartFile = null; + try { + FileInputStream input = new FileInputStream(file); + multipartFile = new MockMultipartFile("file", + file.getName(), + "audio/mpeg", + input); + }catch (Exception e){ + e.printStackTrace(); + throw new FileConvertException(ErrorStatus._FILE_CONVERT_ERROR); + } return multipartFile; } } From 89f6aeac49386b020afdc89ef6bd037150f3268c Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:52:15 +0900 Subject: [PATCH 04/12] =?UTF-8?q?:sparkles:=20FEAT.=20historyId=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=97=B0?= =?UTF-8?q?=EA=B4=80=20=EA=B4=80=EA=B3=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UserTTSSegment.java User ManyToOne으로 변경 --- .../java/fairytale/tbd/domain/voice/entity/TTSSegment.java | 3 --- .../fairytale/tbd/domain/voice/entity/UserTTSSegment.java | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/fairytale/tbd/domain/voice/entity/TTSSegment.java b/src/main/java/fairytale/tbd/domain/voice/entity/TTSSegment.java index 9864204..9fbc121 100644 --- a/src/main/java/fairytale/tbd/domain/voice/entity/TTSSegment.java +++ b/src/main/java/fairytale/tbd/domain/voice/entity/TTSSegment.java @@ -30,9 +30,6 @@ public class TTSSegment extends BaseEntity { @Column(name = "text_to_speech_segment_url") private String url; - @Column(name = "history_id") - private String historyId; - @OneToOne(fetch = FetchType.EAGER) @JoinColumn(name = "fairytale_segment_id") private Segment segment; diff --git a/src/main/java/fairytale/tbd/domain/voice/entity/UserTTSSegment.java b/src/main/java/fairytale/tbd/domain/voice/entity/UserTTSSegment.java index 84345bf..1f4bc44 100644 --- a/src/main/java/fairytale/tbd/domain/voice/entity/UserTTSSegment.java +++ b/src/main/java/fairytale/tbd/domain/voice/entity/UserTTSSegment.java @@ -10,7 +10,6 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Builder; @@ -29,9 +28,6 @@ public class UserTTSSegment extends BaseEntity { @Column(name = "uset_text_to_speech_segment_id") private Long id; - @Column(name = "history_id", nullable = false) - private String historyId; - @Column(name = "user_text_to_speech_segment_url") private String url; @@ -39,7 +35,7 @@ public class UserTTSSegment extends BaseEntity { @JoinColumn(name = "user_id") private User user; - @OneToOne(fetch = FetchType.EAGER) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "fairytale_segment_id") private Segment segment; From 476024f1673b0059cd7df09812567fbf6ce07e08 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:52:52 +0900 Subject: [PATCH 05/12] =?UTF-8?q?:recycle:=20REFACTOR.=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/VoiceCommandServiceImpl.java | 76 +++++++++++++++---- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandServiceImpl.java b/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandServiceImpl.java index eaa6585..7c9708e 100644 --- a/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandServiceImpl.java +++ b/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandServiceImpl.java @@ -16,19 +16,21 @@ import fairytale.tbd.domain.voice.converter.VoiceConverter; import fairytale.tbd.domain.voice.entity.Segment; import fairytale.tbd.domain.voice.entity.TTSSegment; +import fairytale.tbd.domain.voice.entity.UserTTSSegment; import fairytale.tbd.domain.voice.entity.Voice; import fairytale.tbd.domain.voice.entity.VoiceSample; import fairytale.tbd.domain.voice.exception.ExistVoiceException; +import fairytale.tbd.domain.voice.exception.VoiceNotFoundException; import fairytale.tbd.domain.voice.exception.VoiceSaveErrorException; import fairytale.tbd.domain.voice.repository.SegmentRepository; import fairytale.tbd.domain.voice.repository.TTSSegmentRepository; +import fairytale.tbd.domain.voice.repository.UserTTSSegmentRepository; import fairytale.tbd.domain.voice.repository.VoiceRepository; import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO; import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO; import fairytale.tbd.global.aws.s3.AmazonS3Manager; import fairytale.tbd.global.elevenlabs.ElevenlabsManager; import fairytale.tbd.global.enums.statuscode.ErrorStatus; -import fairytale.tbd.global.exception.GeneralException; import fairytale.tbd.global.util.FileConverter; import lombok.RequiredArgsConstructor; @@ -45,9 +47,11 @@ public class VoiceCommandServiceImpl implements VoiceCommandService { private final FairytaleRepository fairytaleRepository; private final SegmentRepository segmentRepository; private final TTSSegmentRepository ttsSegmentRepository; + private final UserTTSSegmentRepository userTTSSegmentRepository; /** * ElevenLabs Voice 추가 + * * @param request MultiPartFile sample 사용자 녹음 파일 */ @@ -83,39 +87,79 @@ public Voice uploadVoice(VoiceRequestDTO.AddVoiceDTO request, User user) { return voice; } + /** + * 동화 내부 텍스트 문장을 추가하는 메서드 + * 기본 음성으로 변환한 데이터도 저장 + */ @Transactional @Override public VoiceResponseDTO.AddTTSSegmentResultDTO addTTSSegment(VoiceRequestDTO.AddSegmentDTO request) { + // TODO Bean Validation Fairytale fairytale = fairytaleRepository.findById(request.getFairytaleId()) - .orElseThrow(() -> new FairytaleNotFoundException(ErrorStatus._FAIRYTALE_NOT_FOUND)); + .orElseThrow(() -> new FairytaleNotFoundException(ErrorStatus._FAIRYTALE_NOT_FOUND)); // 동화가 없는 경우 Segment segment = VoiceConverter.toSegment(request); fairytale.addSegment(segment); segmentRepository.save(segment); - File file = elevenlabsManager.elevenLabsTTS(segment.getContext(), segment.getVoiceType()); - - String uuid = UUID.randomUUID().toString(); + TTSSegment save = saveTTSSegment(segment); + return VoiceConverter.toAddSegmentResultDTO(save, segment.getId()); + } - MultipartFile multipartFile; - try { - multipartFile = FileConverter.toMultipartFile(file, uuid); - } catch (Exception e) { - LOGGER.error("MultipartFile 변환 도중 에러 발생"); - throw new GeneralException(ErrorStatus._INTERNAL_SERVER_ERROR); - } + /** + * 기본 제공 음성으로 변환한 후, 저장하는 메서드 + */ + @Override + public TTSSegment saveTTSSegment(Segment segment) { + File file = elevenlabsManager.elevenLabsTTS(segment.getContext(), segment.getVoiceType()); - String savePath = amazonS3Manager.uploadFile( - amazonS3Manager.generateS3SavePath(amazonS3Manager.TTS_COMMON_VOICE_PATH, uuid), multipartFile); + String savePath = saveSegmentFile(file); TTSSegment ttsSegment = TTSSegment.builder() .url(savePath) .segment(segment) .build(); - TTSSegment save = ttsSegmentRepository.save(ttsSegment); - return VoiceConverter.toAddSegmentResultDTO(save, segment.getId()); + return ttsSegmentRepository.save(ttsSegment); + } + + /** + * 동화 문장을 사용자의 음성으로 변환한 후, 저장하는 메서드 + */ + @Override + public UserTTSSegment saveUserTTSSegment(User user, Segment segment) { + LOGGER.info("saveUserTTSSegment Start"); + + // 사용자의 음성이 저장되지 않은 경우 + Voice userVoice = user.getVoice(); + if (userVoice == null) { + throw new VoiceNotFoundException(ErrorStatus._VOICE_NOT_FOUND); + } + + File file = elevenlabsManager.elevenLabsTTS(segment.getContext(), userVoice.getKeyId()); + + String savedUrl = saveSegmentFile(file); + + UserTTSSegment userTTSSegment = UserTTSSegment.builder() + .user(user) + .url(savedUrl) + .build(); + + segment.addUserTTSSegment(userTTSSegment); + return userTTSSegmentRepository.save(userTTSSegment); } + /** + * ElevenLabs의 TTS 호출로 반환 된 File객체를 저장하는 메서드 + */ + private String saveSegmentFile(File file) { + String fileName = UUID.randomUUID().toString(); + + // MultipartFile 형태로 변환 -> S3에서는 File형태의 저장 지원 X + MultipartFile multipartFile = FileConverter.toMultipartFile(file, fileName); + String savedUrl = amazonS3Manager.uploadFile( + amazonS3Manager.generateS3SavePath(amazonS3Manager.TTS_USER_VOICE_PATH, fileName), multipartFile); + return savedUrl; + } } From 468c8f809ef55dcedec59bc4c449f3d2b17276f8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:53:43 +0900 Subject: [PATCH 06/12] =?UTF-8?q?:bug:=20FIX.=20isMainCharacter=20boolean?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tbd/domain/voice/web/dto/VoiceRequestDTO.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceRequestDTO.java b/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceRequestDTO.java index 67db296..d111089 100644 --- a/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceRequestDTO.java +++ b/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceRequestDTO.java @@ -2,10 +2,13 @@ import org.springframework.web.multipart.MultipartFile; +import com.fasterxml.jackson.annotation.JsonProperty; + import fairytale.tbd.domain.voice.enums.VoiceType; import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; +import lombok.ToString; public class VoiceRequestDTO { @Getter @@ -18,12 +21,15 @@ public static class AddVoiceDTO { @Getter @Setter + @ToString public static class AddSegmentDTO { private String context; + @JsonProperty("isMainCharacter") private boolean isMainCharacter; private VoiceType voiceType; private Long fairytaleId; - private Long segmentNum; + private Double segmentNum; + private Long pageNum; } } From 601e5e9ed0f5599d9155bb64fe6841bb1c50e39a Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:54:18 +0900 Subject: [PATCH 07/12] =?UTF-8?q?:art:=20CHORE.=20=EC=9D=91=EB=8B=B5=20DTO?= =?UTF-8?q?=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../voice/converter/VoiceConverter.java | 2 ++ .../voice/web/dto/VoiceResponseDTO.java | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/main/java/fairytale/tbd/domain/voice/converter/VoiceConverter.java b/src/main/java/fairytale/tbd/domain/voice/converter/VoiceConverter.java index f27ff9e..a75ed5d 100644 --- a/src/main/java/fairytale/tbd/domain/voice/converter/VoiceConverter.java +++ b/src/main/java/fairytale/tbd/domain/voice/converter/VoiceConverter.java @@ -30,6 +30,8 @@ public static Segment toSegment(VoiceRequestDTO.AddSegmentDTO request) { .isMainCharacter(request.isMainCharacter()) .voiceType(request.getVoiceType()) .num(request.getSegmentNum()) + .userTTSSegmentList(new ArrayList<>()) + .pageNum(request.getPageNum()) .build(); } diff --git a/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceResponseDTO.java b/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceResponseDTO.java index 31cb8f9..09fc8c3 100644 --- a/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceResponseDTO.java +++ b/src/main/java/fairytale/tbd/domain/voice/web/dto/VoiceResponseDTO.java @@ -1,6 +1,8 @@ package fairytale.tbd.domain.voice.web.dto; import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; import lombok.AllArgsConstructor; import lombok.Builder; @@ -27,4 +29,29 @@ public static class AddTTSSegmentResultDTO { private String url; private LocalDateTime createdAt; } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class GetUserTTSSegmentResultWithDateDTO { + Map> ttsSegmentResultList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class GetUserTTSSegmentResultDTO { + List ttsSegmentList; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class GetUserTTSSegmentResultDetailDTO { + private String audioUrl; + private Long segmentId; + } } From 3548eed9ce43eeff55890b696ef41996996021fc Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:56:38 +0900 Subject: [PATCH 08/12] =?UTF-8?q?:art:=20CHORE.=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=9D=91=EB=8B=B5=20=EC=BD=94=EB=93=9C,=20=EB=A9=94=EC=84=B8?= =?UTF-8?q?=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/fairytale/tbd/global/enums/statuscode/ErrorStatus.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/fairytale/tbd/global/enums/statuscode/ErrorStatus.java b/src/main/java/fairytale/tbd/global/enums/statuscode/ErrorStatus.java index 45a5cbe..377b88a 100644 --- a/src/main/java/fairytale/tbd/global/enums/statuscode/ErrorStatus.java +++ b/src/main/java/fairytale/tbd/global/enums/statuscode/ErrorStatus.java @@ -22,6 +22,8 @@ public enum ErrorStatus implements BaseCode { _EXIST_VOICE(HttpStatus.BAD_REQUEST, "VOICE4001", "이미 존재하는 목소리입니다."), _VOICE_SAVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "VOICE5002", "목소리 저장에 실패했습니다."), _INVALID_VOICE_TYPE(HttpStatus.BAD_REQUEST, "VOICE4002", "올바르지 않은 목소리 종류입니다."), + _VOICE_NOT_FOUND(HttpStatus.BAD_REQUEST, "VOICE4003", "저장되어 있는 사용자 음성이 없습니다."), + _USER_TTS_DUPLCATION(HttpStatus.INTERNAL_SERVER_ERROR, "VOICE5003", "변환된 음성에서 중복되는 데이터가 있습니다."), // User _EXIST_USERNAME(HttpStatus.BAD_REQUEST, "USER4001", "이미 존재하는 닉네임입니다."), From 55e7c9353ff0997f7282cf910f89766b6add4a91 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:57:17 +0900 Subject: [PATCH 09/12] =?UTF-8?q?:sparkles:=20FEAT.=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=B2=88=ED=98=B8=20=EB=B3=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tbd/domain/voice/entity/Segment.java | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/java/fairytale/tbd/domain/voice/entity/Segment.java b/src/main/java/fairytale/tbd/domain/voice/entity/Segment.java index 6dd89ac..e8076e4 100644 --- a/src/main/java/fairytale/tbd/domain/voice/entity/Segment.java +++ b/src/main/java/fairytale/tbd/domain/voice/entity/Segment.java @@ -1,4 +1,7 @@ package fairytale.tbd.domain.voice.entity; + +import java.util.List; + import fairytale.tbd.domain.fairytale.entity.Fairytale; import fairytale.tbd.domain.voice.enums.VoiceType; import fairytale.tbd.global.entity.BaseEntity; @@ -11,6 +14,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; @@ -24,7 +28,7 @@ @NoArgsConstructor @AllArgsConstructor @Table(name = "fairytale_segment") -public class Segment extends BaseEntity { +public class Segment extends BaseEntity implements Comparable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "fairytale_segment_id") @@ -40,7 +44,10 @@ public class Segment extends BaseEntity { private VoiceType voiceType; @Column(name = "segment_num", nullable = false) - private Long num; + private Double num; + + @Column(name = "page_num", nullable = false) + private Long pageNum; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "fairytale_id", nullable = false) @@ -49,8 +56,21 @@ public class Segment extends BaseEntity { @OneToOne(mappedBy = "segment", cascade = CascadeType.ALL, orphanRemoval = true) private TTSSegment ttsSegment; - @OneToOne(mappedBy = "segment", cascade = CascadeType.ALL, orphanRemoval = true) - private UserTTSSegment userTTSSegment; + @OneToMany(mappedBy = "segment", cascade = CascadeType.ALL, orphanRemoval = true) + private List userTTSSegmentList; + + @Override + public int compareTo(Segment segment) { + if (this.pageNum == segment.pageNum) { + if (this.num < segment.num) { + return -1; + } else if (this.num == segment.num) { + return 0; + } else + return 1; + } + return 1; + } // 연관 관계 편의 메서드 @@ -62,8 +82,8 @@ public void setTtsSegment(TTSSegment ttsSegment) { this.ttsSegment = ttsSegment; } - public void setUserTTSSegment(UserTTSSegment userTTSSegment) { - this.userTTSSegment = userTTSSegment; + public void addUserTTSSegment(UserTTSSegment userTTSSegment) { + userTTSSegmentList.add(userTTSSegment); + userTTSSegment.setSegment(this); } - } From 4c65afcf1575c198365b4448ef6cddb335466aaa Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:58:30 +0900 Subject: [PATCH 10/12] =?UTF-8?q?:sparkles:=20FEAT.=20=EB=8F=99=ED=99=94?= =?UTF-8?q?=20=EB=82=B4=EB=B6=80=20=EB=AC=B8=EC=9E=A5=EC=9D=84=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=9D=8C=EC=84=B1=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=ED=95=98=EC=97=AC=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=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 --- .../repository/FairytaleRepository.java | 4 + .../repository/TTSSegmentRepository.java | 3 + .../repository/UserTTSSegmentRepository.java | 15 +++ .../voice/service/VoiceQueryService.java | 12 +++ .../voice/service/VoiceQueryServiceImpl.java | 94 +++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 src/main/java/fairytale/tbd/domain/voice/repository/UserTTSSegmentRepository.java create mode 100644 src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryService.java create mode 100644 src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryServiceImpl.java diff --git a/src/main/java/fairytale/tbd/domain/fairytale/repository/FairytaleRepository.java b/src/main/java/fairytale/tbd/domain/fairytale/repository/FairytaleRepository.java index 634e05c..4175fa2 100644 --- a/src/main/java/fairytale/tbd/domain/fairytale/repository/FairytaleRepository.java +++ b/src/main/java/fairytale/tbd/domain/fairytale/repository/FairytaleRepository.java @@ -1,5 +1,7 @@ package fairytale.tbd.domain.fairytale.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -8,4 +10,6 @@ @Repository public interface FairytaleRepository extends JpaRepository { boolean existsByName(String name); + + Optional findByName(String name); } diff --git a/src/main/java/fairytale/tbd/domain/voice/repository/TTSSegmentRepository.java b/src/main/java/fairytale/tbd/domain/voice/repository/TTSSegmentRepository.java index ceaf334..1d4510b 100644 --- a/src/main/java/fairytale/tbd/domain/voice/repository/TTSSegmentRepository.java +++ b/src/main/java/fairytale/tbd/domain/voice/repository/TTSSegmentRepository.java @@ -1,5 +1,7 @@ package fairytale.tbd.domain.voice.repository; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -7,4 +9,5 @@ @Repository public interface TTSSegmentRepository extends JpaRepository { + Optional findBySegmentId(Long segmentId); } diff --git a/src/main/java/fairytale/tbd/domain/voice/repository/UserTTSSegmentRepository.java b/src/main/java/fairytale/tbd/domain/voice/repository/UserTTSSegmentRepository.java new file mode 100644 index 0000000..2992c95 --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/voice/repository/UserTTSSegmentRepository.java @@ -0,0 +1,15 @@ +package fairytale.tbd.domain.voice.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import fairytale.tbd.domain.user.entity.User; +import fairytale.tbd.domain.voice.entity.Segment; +import fairytale.tbd.domain.voice.entity.UserTTSSegment; + +@Repository +public interface UserTTSSegmentRepository extends JpaRepository { + Optional findByUserAndSegment(User user, Segment segment); +} diff --git a/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryService.java b/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryService.java new file mode 100644 index 0000000..5b2158d --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryService.java @@ -0,0 +1,12 @@ +package fairytale.tbd.domain.voice.service; + +import java.util.List; +import java.util.Map; + +import fairytale.tbd.domain.user.entity.User; +import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO; + +public interface VoiceQueryService { + Map> getUserTTSSegmentList(User user, + String fairytaleName); +} diff --git a/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryServiceImpl.java b/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryServiceImpl.java new file mode 100644 index 0000000..c0dc793 --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/voice/service/VoiceQueryServiceImpl.java @@ -0,0 +1,94 @@ +package fairytale.tbd.domain.voice.service; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import fairytale.tbd.domain.fairytale.entity.Fairytale; +import fairytale.tbd.domain.fairytale.exception.FairytaleNotFoundException; +import fairytale.tbd.domain.fairytale.repository.FairytaleRepository; +import fairytale.tbd.domain.user.entity.User; +import fairytale.tbd.domain.voice.entity.Segment; +import fairytale.tbd.domain.voice.entity.TTSSegment; +import fairytale.tbd.domain.voice.entity.UserTTSSegment; +import fairytale.tbd.domain.voice.repository.TTSSegmentRepository; +import fairytale.tbd.domain.voice.repository.UserTTSSegmentRepository; +import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO; +import fairytale.tbd.global.enums.statuscode.ErrorStatus; +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class VoiceQueryServiceImpl implements VoiceQueryService { + + private static final Logger LOGGER = LogManager.getLogger(VoiceQueryServiceImpl.class); + + private final VoiceCommandService voiceCommandService; + private final FairytaleRepository fairytaleRepository; + private final UserTTSSegmentRepository userTTSSegmentRepository; + private final TTSSegmentRepository ttsSegmentRepository; + + /** + * 특정 사용자의 목소리로 변경한 음성과 기본 모든 음성을 가져옴 + * 동화의 문장 목록들을 탐색하며 + * 주인공이라면 사용자의 음성을 (만약 존재하지 않는다면 생성 후 저장) + * 주인공이 아니라면 기본 음성을 (마찬가지로 존재하지 않는다면 생성 후 저장) + * 받은 후 문장의 페이지 번호를 키값으로 Map 형태로 반환 (문장 순서대로 정렬) + */ + @Transactional + @Override + public Map> getUserTTSSegmentList(User user, + String fairytaleName) { + // 동화 이름이 유효한지 검증 + // TODO Bean Validation으로 넘기기 + Fairytale fairytale = fairytaleRepository.findByName(fairytaleName) + .orElseThrow(() -> new FairytaleNotFoundException(ErrorStatus._FAIRYTALE_NOT_FOUND)); + + // 동화의 문장 목록을 불러온 후, 페이지 번호, 문장 번호에 대해 오름차순 정렬 + List segmentList = fairytale.getSegmentList(); + Collections.sort(segmentList); + + Map> result = new HashMap<>(); + + for (Segment segment : segmentList) { + VoiceResponseDTO.GetUserTTSSegmentResultDetailDTO resultDTO = null; + // TODO 상속관계로 변경, ASYNC + if (segment.isMainCharacter()) { + UserTTSSegment userTTSSegment = userTTSSegmentRepository.findByUserAndSegment(user, segment) + .orElseGet(() -> voiceCommandService.saveUserTTSSegment(user, segment)); + + resultDTO = VoiceResponseDTO.GetUserTTSSegmentResultDetailDTO.builder() + .segmentId(segment.getId()) + .audioUrl(userTTSSegment.getUrl()) + .build(); + } else { + + TTSSegment ttsSegment = ttsSegmentRepository.findBySegmentId(segment.getId()) + .orElseGet(() -> voiceCommandService.saveTTSSegment(segment)); + + resultDTO = VoiceResponseDTO.GetUserTTSSegmentResultDetailDTO.builder() + .segmentId(segment.getId()) + .audioUrl(ttsSegment.getUrl()) + .build(); + } + if (resultDTO == null) { + LOGGER.error("::: 음성 목록을 가져오는 중 에러 발생 :::"); + throw new RuntimeException("음성 목록을 가져오는 중 에러가 발생했습니다."); + } + if (!result.containsKey(segment.getPageNum())) { + result.put(segment.getPageNum(), new ArrayList()); + } + result.get(segment.getPageNum()).add(resultDTO); + } + return result; + } + +} From 3eaf0591945b644bbb3531c470668fb57df3adc4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:59:01 +0900 Subject: [PATCH 11/12] =?UTF-8?q?:recycle:=20REFACTOR.=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=A5=20=EC=A0=80=EC=9E=A5=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tbd/domain/voice/service/VoiceCommandService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandService.java b/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandService.java index 45249f6..e140bef 100644 --- a/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandService.java +++ b/src/main/java/fairytale/tbd/domain/voice/service/VoiceCommandService.java @@ -1,6 +1,9 @@ package fairytale.tbd.domain.voice.service; import fairytale.tbd.domain.user.entity.User; +import fairytale.tbd.domain.voice.entity.Segment; +import fairytale.tbd.domain.voice.entity.TTSSegment; +import fairytale.tbd.domain.voice.entity.UserTTSSegment; import fairytale.tbd.domain.voice.entity.Voice; import fairytale.tbd.domain.voice.web.dto.VoiceRequestDTO; import fairytale.tbd.domain.voice.web.dto.VoiceResponseDTO; @@ -9,4 +12,8 @@ public interface VoiceCommandService { Voice uploadVoice(VoiceRequestDTO.AddVoiceDTO request, User user); VoiceResponseDTO.AddTTSSegmentResultDTO addTTSSegment(VoiceRequestDTO.AddSegmentDTO request); + + TTSSegment saveTTSSegment(Segment segment); + + UserTTSSegment saveUserTTSSegment(User user, Segment segment); } From a6ab09540a48902f17f9a69c2c29e8dabfcb67bf Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 3 May 2024 02:59:22 +0900 Subject: [PATCH 12/12] =?UTF-8?q?:art:=20CHORE.=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9D=8C=EC=84=B1=20=EC=A1=B0=ED=9A=8C=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/voice/exception/VoiceNotFoundException.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/fairytale/tbd/domain/voice/exception/VoiceNotFoundException.java diff --git a/src/main/java/fairytale/tbd/domain/voice/exception/VoiceNotFoundException.java b/src/main/java/fairytale/tbd/domain/voice/exception/VoiceNotFoundException.java new file mode 100644 index 0000000..3844c71 --- /dev/null +++ b/src/main/java/fairytale/tbd/domain/voice/exception/VoiceNotFoundException.java @@ -0,0 +1,10 @@ +package fairytale.tbd.domain.voice.exception; + +import fairytale.tbd.global.enums.statuscode.BaseCode; +import fairytale.tbd.global.exception.GeneralException; + +public class VoiceNotFoundException extends GeneralException { + public VoiceNotFoundException(BaseCode errorStatus) { + super(errorStatus); + } +}