From bc83b905582be84d03880653dce327820c1de274 Mon Sep 17 00:00:00 2001 From: LgE02 Date: Tue, 16 Sep 2025 14:27:55 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat=20:=20=EC=95=8C=EB=A6=BC,=20=EC=A7=84?= =?UTF-8?q?=EB=8F=99=20=EC=83=81=ED=83=9C=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/SoundSettingRepository.java | 2 ++ .../service/SoundSettingService.java | 7 ++++ .../service/SoundSettingServiceImpl.java | 33 +++++++++++++++++++ .../controller/SoundSettingController.java | 20 ++++++++--- .../soundsetting/web/dto/SoundSettingRes.java | 30 +++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/sosaw/sosaw/domain/soundsetting/web/dto/SoundSettingRes.java diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java index ad9058e..390768e 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java @@ -4,6 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository @@ -15,4 +16,5 @@ public interface SoundSettingRepository extends JpaRepository findByUserUserIdAndCustomSoundId(Long userId, Long customId); Optional findByUserUserIdAndBasicSoundId(Long userId, Long basicId); + List findByUserUserId(Long userId); } diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingService.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingService.java index 380302f..05ee57b 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingService.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingService.java @@ -1,11 +1,18 @@ package com.sosaw.sosaw.domain.soundsetting.service; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundAlarmUpdateReq; +import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq; import com.sosaw.sosaw.domain.user.entity.User; +import com.sosaw.sosaw.global.security.CustomUserDetails; + +import java.util.List; public interface SoundSettingService { + // 알림 설정 조회 + List getAllSoundSetting(User user); + // 알람 유무 수정 void updateAlarm(User user, SoundAlarmUpdateReq req); void updateCustomAlarm(User user, SoundAlarmUpdateReq req); diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java index 1fa5828..797e24b 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java @@ -8,12 +8,15 @@ import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; import com.sosaw.sosaw.domain.soundsetting.repository.SoundSettingRepository; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundAlarmUpdateReq; +import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq; import com.sosaw.sosaw.domain.user.entity.User; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor public class SoundSettingServiceImpl implements SoundSettingService { @@ -22,6 +25,36 @@ public class SoundSettingServiceImpl implements SoundSettingService { private final CustomSoundRepository customSoundRepository; private final SoundSettingRepository soundSettingRepository; + @Override + @Transactional + public List getAllSoundSetting(User user) { + //모든 사운드 설정 조회 + List settings = soundSettingRepository.findByUserUserId(user.getUserId()); + + //기본 사운드 설정 조회 + List basicSounds = basicSoundRepository.findAll(); + + //기본 사운드 존재 확인 + for(BasicSound basicSound : basicSounds) { + boolean exists = false; + for(SoundSetting setting : settings) { + if(setting.getBasicSound() != null && + setting.getBasicSound().getId().equals(basicSound.getId())) { + exists = true; + break; + } + } + if(!exists) { + SoundSetting newSetting = SoundSetting.createForBasic(user, basicSound); + soundSettingRepository.save(newSetting); + settings.add(newSetting); + } + } + + + return settings.stream().map(SoundSettingRes::from).toList(); + } + @Override @Transactional public void updateAlarm(User user, SoundAlarmUpdateReq req) { diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/controller/SoundSettingController.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/controller/SoundSettingController.java index 9b0ae55..a76fa97 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/controller/SoundSettingController.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/controller/SoundSettingController.java @@ -1,19 +1,21 @@ package com.sosaw.sosaw.domain.soundsetting.web.controller; +import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; import com.sosaw.sosaw.domain.soundsetting.service.SoundSettingService; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundAlarmUpdateReq; +import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq; import com.sosaw.sosaw.global.response.SuccessResponse; import com.sosaw.sosaw.global.security.CustomUserDetails; +import com.sun.net.httpserver.Authenticator; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -22,6 +24,16 @@ public class SoundSettingController { private final SoundSettingService soundSettingService; + //사운드 종류/알림/진동 조회 + @GetMapping("") + public ResponseEntity> getAllSoundSettings( + @AuthenticationPrincipal CustomUserDetails userDetails + ) { + List res = soundSettingService.getAllSoundSetting(userDetails.getUser()); + return ResponseEntity.status( + HttpStatus.OK).body(SuccessResponse.ok(res)); + } + // 알람 유무 수정 @PutMapping("/alarm") public ResponseEntity> updateAlarm( diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/dto/SoundSettingRes.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/dto/SoundSettingRes.java new file mode 100644 index 0000000..479c7ba --- /dev/null +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/web/dto/SoundSettingRes.java @@ -0,0 +1,30 @@ +package com.sosaw.sosaw.domain.soundsetting.web.dto; + +import com.sosaw.sosaw.domain.customsound.entity.enums.Color; +import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; +import com.sosaw.sosaw.domain.soundsetting.entity.enums.SoundKind; + +public record SoundSettingRes( + String soundName, + String emoji, + Color color, + boolean alarmEnabled, + int vibrationLevel + +) { + public static SoundSettingRes from(SoundSetting setting) { + return new SoundSettingRes( + setting.getSoundKind() == SoundKind.DEFAULT + ? setting.getBasicSound().getSoundType().toString() + : setting.getCustomSound().getCustomName(), + setting.getSoundKind() == SoundKind.DEFAULT + ? null + : setting.getCustomSound().getEmoji(), + setting.getSoundKind() == SoundKind.DEFAULT + ? null + : setting.getCustomSound().getColor(), + setting.isAlarmEnabled(), + setting.getVibrationLevel() + ); + } +} From 5bfd8c854def840ff4554984dc0c3e890616ce2c Mon Sep 17 00:00:00 2001 From: LgE02 Date: Wed, 17 Sep 2025 10:47:16 +0900 Subject: [PATCH 2/6] =?UTF-8?q?feat=20:=20FastApi=EC=9D=98=20target=5Flabe?= =?UTF-8?q?ls=EA=B3=BC=20=EB=A7=A4=ED=95=91=EB=90=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/enums/BasicSoundType.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/sosaw/sosaw/domain/basicsound/entity/enums/BasicSoundType.java b/src/main/java/com/sosaw/sosaw/domain/basicsound/entity/enums/BasicSoundType.java index 8000a87..d605466 100644 --- a/src/main/java/com/sosaw/sosaw/domain/basicsound/entity/enums/BasicSoundType.java +++ b/src/main/java/com/sosaw/sosaw/domain/basicsound/entity/enums/BasicSoundType.java @@ -1,16 +1,33 @@ package com.sosaw.sosaw.domain.basicsound.entity.enums; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor public enum BasicSoundType { - DOG_BARK, // 강아지 짓는 소리 - CAT_MEOW, // 고양이 소리 - HUMAN_LAUGH, // 사람 웃는 소리 - BABY_CRY, // 아기 우는 소리 - PHONE_RING, // 전화벨 소리 - DOORBELL, // 초인종 소리 - DOOR_OPEN_CLOSE, // 문 여닫는 소리 - KNOCK, // 노크 소리 - FIRE_ALARM, // 화재 경보기 소리 - MICROWAVE, // 전자레인지 소리 - CAR_HORN, // 경적 소리 - SIREN // 비상 경보음 소리 + DOG_BARK("Dog Bark"), // 강아지 짓는 소리 + CAT_MEOW("Cat Meow"), // 고양이 소리 + HUMAN_LAUGH("Laughing"), // 사람 웃는 소리 + BABY_CRY("Baby Cry"), // 아기 우는 소리 + PHONE_RING("Phone Ring"), // 전화벨 소리 + DOORBELL("Doorbell"), // 초인종 소리 + DOOR_OPEN_CLOSE("Door In-Use"), // 문 여닫는 소리 + KNOCK("Knocking"), // 노크 소리 + FIRE_ALARM("Fire/Smoke Alarm"), // 화재 경보기 소리 + MICROWAVE("Microwave"), // 전자레인지 소리 + CAR_HORN("Car Honk"), // 경적 소리 + SIREN("Siren"), // 비상 경보음 소리 + UNKNOWN("Unknown"); + + private final String label; + + public static BasicSoundType fromLabel(String find) { + return Arrays.stream(values()) + .filter(e -> e.label.equalsIgnoreCase(find)) + .findFirst() + .orElse(UNKNOWN); // 매칭 없으면 null + } } From 2e94262e4009e70af4be5ece4528514237d9bbc8 Mon Sep 17 00:00:00 2001 From: LgE02 Date: Wed, 17 Sep 2025 10:53:11 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat=20:=20=EA=B8=B0=EB=B3=B8=EC=86=8C?= =?UTF-8?q?=EB=A6=AC=20=EC=97=90=EB=9F=AC=20=EA=B5=AC=ED=98=84,=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=EC=97=90=EB=9F=AC=20=EB=AA=85?= =?UTF-8?q?=EC=B9=AD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/BasicSoundErrorCode.java | 17 +++++++++++++++++ .../exception/NotFoundBasicSoundException.java | 9 +++++++++ .../exception/CustomSoundErrorCode.java | 2 +- .../exception/NotFoundCustomSoundException.java | 9 +++++++++ .../exception/NotFoundSoundException.java | 9 --------- 5 files changed, 36 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/sosaw/sosaw/domain/basicsound/exception/BasicSoundErrorCode.java create mode 100644 src/main/java/com/sosaw/sosaw/domain/basicsound/exception/NotFoundBasicSoundException.java create mode 100644 src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundCustomSoundException.java delete mode 100644 src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundSoundException.java diff --git a/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/BasicSoundErrorCode.java b/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/BasicSoundErrorCode.java new file mode 100644 index 0000000..6afaf62 --- /dev/null +++ b/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/BasicSoundErrorCode.java @@ -0,0 +1,17 @@ +package com.sosaw.sosaw.domain.basicsound.exception; + +import com.sosaw.sosaw.global.response.code.BaseResponseCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import static com.sosaw.sosaw.global.constant.StaticValue.NOT_FOUND; + +@Getter +@AllArgsConstructor +public enum BasicSoundErrorCode implements BaseResponseCode { + NOT_FOUND_BASIC_SOUND_404("BASIC_SOUND_404_1", NOT_FOUND, "해당 기본 소리를 찾을 수 없습니다."); + + private final String code; + private final int httpStatus; + private final String message; +} diff --git a/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/NotFoundBasicSoundException.java b/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/NotFoundBasicSoundException.java new file mode 100644 index 0000000..9cf6e56 --- /dev/null +++ b/src/main/java/com/sosaw/sosaw/domain/basicsound/exception/NotFoundBasicSoundException.java @@ -0,0 +1,9 @@ +package com.sosaw.sosaw.domain.basicsound.exception; + +import com.sosaw.sosaw.global.exception.BaseException; + +public class NotFoundBasicSoundException extends BaseException { + public NotFoundBasicSoundException() { + super(BasicSoundErrorCode.NOT_FOUND_BASIC_SOUND_404); + } +} diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/exception/CustomSoundErrorCode.java b/src/main/java/com/sosaw/sosaw/domain/customsound/exception/CustomSoundErrorCode.java index 7c792d1..396f180 100644 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/exception/CustomSoundErrorCode.java +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/exception/CustomSoundErrorCode.java @@ -12,7 +12,7 @@ public enum CustomSoundErrorCode implements BaseResponseCode { UNSUPPORTED_EXTENSION_415("CUSTOM_SOUND_415", UNSUPPORTED_MEDIA_TYPE, "지원하지 않는 파일 형식입니다. (.wav만 허용)"), FILE_PROCESS_FAILED_500("CUSTOM_SOUND_500_1", INTERNAL_SERVER_ERROR, "파일 처리 중 오류가 발생했습니다."), FASTAPI_CALL_FAILED_500("CUSTOM_SOUND_500_2", INTERNAL_SERVER_ERROR, "FastAPI MFCC 호출 실패"), - NOT_FOUND_SOUND_404("CUSTOM_SOUND_404_1", NOT_FOUND, "해당 소리를 찾을 수 없습니다."); + NOT_FOUND_CUSTOM_SOUND_404("CUSTOM_SOUND_404_1", NOT_FOUND, "해당 커스텀 소리를 찾을 수 없습니다."); private final String code; private final int httpStatus; diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundCustomSoundException.java b/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundCustomSoundException.java new file mode 100644 index 0000000..1cf6218 --- /dev/null +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundCustomSoundException.java @@ -0,0 +1,9 @@ +package com.sosaw.sosaw.domain.customsound.exception; + +import com.sosaw.sosaw.global.exception.BaseException; + +public class NotFoundCustomSoundException extends BaseException { + public NotFoundCustomSoundException() { + super(CustomSoundErrorCode.NOT_FOUND_CUSTOM_SOUND_404); + } +} diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundSoundException.java b/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundSoundException.java deleted file mode 100644 index c3aace8..0000000 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/exception/NotFoundSoundException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sosaw.sosaw.domain.customsound.exception; - -import com.sosaw.sosaw.global.exception.BaseException; - -public class NotFoundSoundException extends BaseException { - public NotFoundSoundException() { - super(CustomSoundErrorCode.NOT_FOUND_SOUND_404); - } -} From e3d834e66d1872f663e386e88b6aafeb1a227610 Mon Sep 17 00:00:00 2001 From: LgE02 Date: Wed, 17 Sep 2025 10:53:38 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat=20:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/SoundSettingServiceImpl.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java index 797e24b..80dcbc3 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/service/SoundSettingServiceImpl.java @@ -2,7 +2,7 @@ import com.sosaw.sosaw.domain.basicsound.entity.BasicSound; import com.sosaw.sosaw.domain.basicsound.repository.BasicSoundRepository; -import com.sosaw.sosaw.domain.customsound.exception.NotFoundSoundException; +import com.sosaw.sosaw.domain.customsound.exception.NotFoundCustomSoundException; import com.sosaw.sosaw.domain.customsound.entity.CustomSound; import com.sosaw.sosaw.domain.customsound.repository.CustomSoundRepository; import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; @@ -11,6 +11,8 @@ import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundSettingRes; import com.sosaw.sosaw.domain.soundsetting.web.dto.SoundVibrationUpdateReq; import com.sosaw.sosaw.domain.user.entity.User; +import com.sosaw.sosaw.domain.user.exception.UserNotFoundException; +import com.sosaw.sosaw.domain.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,10 +26,14 @@ public class SoundSettingServiceImpl implements SoundSettingService { private final BasicSoundRepository basicSoundRepository; private final CustomSoundRepository customSoundRepository; private final SoundSettingRepository soundSettingRepository; + private final UserRepository userRepository; @Override @Transactional public List getAllSoundSetting(User user) { + userRepository.findById(user.getUserId()) + .orElseThrow(UserNotFoundException::new); + //모든 사운드 설정 조회 List settings = soundSettingRepository.findByUserUserId(user.getUserId()); @@ -79,7 +85,7 @@ public void updateCustomAlarm(User user, SoundAlarmUpdateReq req) { // 1) 내 커스텀 사운드인지 검증 CustomSound customSound = customSoundRepository .findByIdAndUserUserId(req.getSoundId(), user.getUserId()) - .orElseThrow(NotFoundSoundException::new); + .orElseThrow(NotFoundCustomSoundException::new); // 2) 설정 조회 or 생성 SoundSetting setting = soundSettingRepository @@ -99,7 +105,7 @@ public void updateCustomVibration(User user, SoundVibrationUpdateReq req) { // 1) 내 커스텀 사운드인지 검증 CustomSound customSound = customSoundRepository .findByIdAndUserUserId(req.getSoundId(), user.getUserId()) - .orElseThrow(NotFoundSoundException::new); + .orElseThrow(NotFoundCustomSoundException::new); // 2) 설정 조회 or 생성 SoundSetting setting = soundSettingRepository @@ -117,7 +123,7 @@ public void updateCustomVibration(User user, SoundVibrationUpdateReq req) { public void updateDefaultAlarm(User user, SoundAlarmUpdateReq req) { // 1) 기본 사운드 검증 BasicSound basicSound = basicSoundRepository.findById(req.getSoundId()) - .orElseThrow(NotFoundSoundException::new); + .orElseThrow(NotFoundCustomSoundException::new); // 2) 설정 조회 or 생성 SoundSetting setting = soundSettingRepository @@ -135,7 +141,7 @@ public void updateDefaultAlarm(User user, SoundAlarmUpdateReq req) { public void updateDefaultVibration(User user, SoundVibrationUpdateReq req) { // 1) 기본 사운드 검증 BasicSound basicSound = basicSoundRepository.findById(req.getSoundId()) - .orElseThrow(NotFoundSoundException::new); + .orElseThrow(NotFoundCustomSoundException::new); // 2) 설정 조회 or 생성 SoundSetting setting = soundSettingRepository From adc82933c4415f796083ad49674ba83e231f443f Mon Sep 17 00:00:00 2001 From: LgE02 Date: Wed, 17 Sep 2025 10:54:23 +0900 Subject: [PATCH 5/6] =?UTF-8?q?feat=20:=20=EC=9D=BC=EC=83=81=EC=83=9D?= =?UTF-8?q?=ED=99=9C=20=EC=86=8C=EB=A6=AC=20=ED=83=90=EC=A7=80=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/BasicSoundRepository.java | 4 ++ .../port/AudioFeatureExtractor.java | 1 + .../repository/CustomSoundRepository.java | 2 +- .../service/CustomSoundServiceImpl.java | 44 +++++++++++++++---- .../customsound/web/dto/SoundMatchRes.java | 17 +++++++ .../repository/SoundSettingRepository.java | 3 ++ .../fastapi/FastApiMfccExtractor.java | 30 +++++++++---- .../fastapi/PythonMFCCService.java | 32 +++++++++++--- 8 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/sosaw/sosaw/domain/basicsound/repository/BasicSoundRepository.java b/src/main/java/com/sosaw/sosaw/domain/basicsound/repository/BasicSoundRepository.java index 7592f5d..2c7f937 100644 --- a/src/main/java/com/sosaw/sosaw/domain/basicsound/repository/BasicSoundRepository.java +++ b/src/main/java/com/sosaw/sosaw/domain/basicsound/repository/BasicSoundRepository.java @@ -1,10 +1,14 @@ package com.sosaw.sosaw.domain.basicsound.repository; import com.sosaw.sosaw.domain.basicsound.entity.BasicSound; +import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface BasicSoundRepository extends JpaRepository { + Optional findBySoundType(BasicSoundType sound); } diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/port/AudioFeatureExtractor.java b/src/main/java/com/sosaw/sosaw/domain/customsound/port/AudioFeatureExtractor.java index c5cc526..182ad6c 100644 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/port/AudioFeatureExtractor.java +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/port/AudioFeatureExtractor.java @@ -4,4 +4,5 @@ public interface AudioFeatureExtractor { float[] extractMfcc(MultipartFile file); + String predict(MultipartFile file); } diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/repository/CustomSoundRepository.java b/src/main/java/com/sosaw/sosaw/domain/customsound/repository/CustomSoundRepository.java index 69948f3..0ca88df 100644 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/repository/CustomSoundRepository.java +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/repository/CustomSoundRepository.java @@ -42,7 +42,7 @@ public interface CustomSoundRepository extends JpaRepository ORDER BY c.mfcc <=> (:mfcc)::vector ASC LIMIT 1 """, nativeQuery = true) - Optional findTopMatchByUserIdWithSimilarity( + SoundMatchRow findTopMatchByUserIdWithSimilarity( @Param("userId") Long userId, @Param("mfcc") String mfccVectorLiteral // "[0.12, -0.03, ...]" 형태의 문자열 ); diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/service/CustomSoundServiceImpl.java b/src/main/java/com/sosaw/sosaw/domain/customsound/service/CustomSoundServiceImpl.java index 716ac74..a6397b9 100644 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/service/CustomSoundServiceImpl.java +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/service/CustomSoundServiceImpl.java @@ -1,7 +1,11 @@ package com.sosaw.sosaw.domain.customsound.service; +import com.sosaw.sosaw.domain.basicsound.entity.BasicSound; +import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType; +import com.sosaw.sosaw.domain.basicsound.exception.NotFoundBasicSoundException; +import com.sosaw.sosaw.domain.basicsound.repository.BasicSoundRepository; import com.sosaw.sosaw.domain.customsound.entity.CustomSound; -import com.sosaw.sosaw.domain.customsound.exception.NotFoundSoundException; +import com.sosaw.sosaw.domain.customsound.exception.NotFoundCustomSoundException; import com.sosaw.sosaw.domain.customsound.port.AudioFeatureExtractor; import com.sosaw.sosaw.domain.customsound.repository.CustomSoundRepository; import com.sosaw.sosaw.domain.customsound.web.dto.SoundMatchRes; @@ -32,6 +36,7 @@ public class CustomSoundServiceImpl implements CustomSoundService{ private final AudioFeatureExtractor audioFeatureExtractor; // 포트 주입 private final SoundSettingRepository soundSettingRepository; private final UserRepository userRepository; + private final BasicSoundRepository basicSoundRepository; @Override @@ -54,7 +59,7 @@ public void upload(SoundUploadReq req, User user) { public void delete(Long customSoundId) { customSoundRepository.findById(customSoundId).ifPresentOrElse( customSoundRepository::delete, - () -> { throw new NotFoundSoundException(); } + () -> { throw new NotFoundCustomSoundException(); } ); } @@ -71,7 +76,7 @@ public List getAllSounds(User user) { @Transactional public void modify(SoundUploadReq req, Long customSoundId) { CustomSound sound = customSoundRepository.findById(customSoundId) - .orElseThrow(NotFoundSoundException::new); + .orElseThrow(NotFoundCustomSoundException::new); float[] mfcc = audioFeatureExtractor.extractMfcc(req.getFile()); sound.replace(req, mfcc); } @@ -86,11 +91,34 @@ public SoundMatchRes match(User user, MultipartFile file) { String literal = FloatArrayVectorConverter.toLiteral(mfcc); SoundMatchRow row = customSoundRepository - .findTopMatchByUserIdWithSimilarity(user.getUserId(), literal) - .orElseThrow(NotFoundSoundException::new); - //TODO: 일상생활 소리 탐지와 사용자 소리 어떤 것을 반환할지, 단일 컷 정확도 기준 필요 - // 일상생활 소리 탐지 부분에서 서비스에서 재공되는 9개의 소리가 아닌 다른 소리 분류시 제외 시켜야 함. - return SoundMatchRes.from(row); + .findTopMatchByUserIdWithSimilarity(user.getUserId(), literal); + + log.info("커스텀 소리 유사도: {}", row.getSimilarity()); + + //1. 커스텀 유사도 + if (row != null && row.getSimilarity() >= 0.997) { + return SoundMatchRes.from(row); + } + + //2. tf모델 예측 + String label = audioFeatureExtractor.predict(file); + + log.info("매핑된 사운드 종류 : {}", label); + + BasicSoundType sound = BasicSoundType.fromLabel(label); + + //커스텀 유사도가 낮고 tf에서도 예측불가능 + if (sound == BasicSoundType.UNKNOWN) { + return SoundMatchRes.unknown(); + } + + BasicSound basicSound = basicSoundRepository.findBySoundType(sound) + .orElseThrow(NotFoundBasicSoundException::new); + + SoundSetting setting = soundSettingRepository.findByUserUserIdAndBasicSound(user.getUserId(), basicSound) + .orElse(SoundSetting.createForBasic(user, basicSound)); + + return SoundMatchRes.fromBasicSound(sound, setting); } } diff --git a/src/main/java/com/sosaw/sosaw/domain/customsound/web/dto/SoundMatchRes.java b/src/main/java/com/sosaw/sosaw/domain/customsound/web/dto/SoundMatchRes.java index 6684818..86d8e8d 100644 --- a/src/main/java/com/sosaw/sosaw/domain/customsound/web/dto/SoundMatchRes.java +++ b/src/main/java/com/sosaw/sosaw/domain/customsound/web/dto/SoundMatchRes.java @@ -1,7 +1,9 @@ package com.sosaw.sosaw.domain.customsound.web.dto; +import com.sosaw.sosaw.domain.basicsound.entity.enums.BasicSoundType; import com.sosaw.sosaw.domain.customsound.entity.enums.Color; import com.sosaw.sosaw.domain.customsound.repository.projection.SoundMatchRow; +import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; public record SoundMatchRes( String soundName, @@ -21,4 +23,19 @@ public static SoundMatchRes from(SoundMatchRow row){ row.getVibration() ); } + + public static SoundMatchRes fromBasicSound(BasicSoundType sound, SoundSetting setting) { + return new SoundMatchRes( + sound.getLabel(), + null, + null, + -1.0, + setting.isAlarmEnabled(), + setting.getVibrationLevel() + ); + } + + public static SoundMatchRes unknown(){ + return new SoundMatchRes("Unknown", null,null,0.0, false,0); + } } diff --git a/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java b/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java index 390768e..8f733fb 100644 --- a/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java +++ b/src/main/java/com/sosaw/sosaw/domain/soundsetting/repository/SoundSettingRepository.java @@ -1,5 +1,6 @@ package com.sosaw.sosaw.domain.soundsetting.repository; +import com.sosaw.sosaw.domain.basicsound.entity.BasicSound; import com.sosaw.sosaw.domain.soundsetting.entity.SoundSetting; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -17,4 +18,6 @@ public interface SoundSettingRepository extends JpaRepository findByUserUserIdAndBasicSoundId(Long userId, Long basicId); List findByUserUserId(Long userId); + + Optional findByUserUserIdAndBasicSound(Long userId, BasicSound basicSound); } diff --git a/src/main/java/com/sosaw/sosaw/global/integration/fastapi/FastApiMfccExtractor.java b/src/main/java/com/sosaw/sosaw/global/integration/fastapi/FastApiMfccExtractor.java index 9715353..ede5972 100644 --- a/src/main/java/com/sosaw/sosaw/global/integration/fastapi/FastApiMfccExtractor.java +++ b/src/main/java/com/sosaw/sosaw/global/integration/fastapi/FastApiMfccExtractor.java @@ -22,23 +22,37 @@ public class FastApiMfccExtractor implements AudioFeatureExtractor { @Override public float[] extractMfcc(MultipartFile file) { - validateWav(file); - - Path tmp = null; + Path tmp = saveTempFile(file); try { - tmp = Files.createTempFile("wav-", ".wav"); - file.transferTo(tmp.toFile()); - List vec = pythonMFCCService.extractMFCC(tmp); return toFloatArray(vec, MFCC_DIM); + } finally { + safeDelete(tmp); + } + } - } catch (IOException e) { - throw new FileProcessFailedException(); + @Override + public String predict(MultipartFile file) { + Path tmp = saveTempFile(file); + try { + return pythonMFCCService.predict(tmp); } finally { safeDelete(tmp); } } + private Path saveTempFile(MultipartFile file) { + validateWav(file); + try { + byte[] data = file.getBytes(); + Path tmp = Files.createTempFile("wav-", ".wav"); + Files.write(tmp, data); + return tmp; + } catch (IOException e) { + throw new FileProcessFailedException(); + } + } + private void validateWav(MultipartFile file) { if (file == null || file.isEmpty()) throw new FileProcessFailedException(); String name = Optional.ofNullable(file.getOriginalFilename()).orElse("upload.wav"); diff --git a/src/main/java/com/sosaw/sosaw/global/integration/fastapi/PythonMFCCService.java b/src/main/java/com/sosaw/sosaw/global/integration/fastapi/PythonMFCCService.java index 8196022..5b3f114 100644 --- a/src/main/java/com/sosaw/sosaw/global/integration/fastapi/PythonMFCCService.java +++ b/src/main/java/com/sosaw/sosaw/global/integration/fastapi/PythonMFCCService.java @@ -2,6 +2,7 @@ import com.sosaw.sosaw.domain.customsound.exception.FastapiCallFailedException; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.FileSystemResource; import org.springframework.http.HttpEntity; @@ -17,6 +18,7 @@ import java.nio.file.Path; import java.util.List; +@Slf4j @Service @RequiredArgsConstructor public class PythonMFCCService { @@ -27,6 +29,18 @@ public class PythonMFCCService { private String fastAPIBaseUrl; public List extractMFCC(Path wavPath) { + @SuppressWarnings("unchecked") + List mfcc = (List) sendFileToFastApi("/extract-mfcc", wavPath, List.class); + return mfcc; + } + + public String predict(Path wavPath) { + PredictResponse res = sendFileToFastApi("/predict", wavPath, PredictResponse.class); + log.info("label : {}, prob:{}", res.label, res.prob); + return res.label(); + } + + private T sendFileToFastApi(String endPoint, Path wavPath, Class responseType) { // multipart/form-data 구성 MultiValueMap body = new LinkedMultiValueMap<>(); body.add("file", new FileSystemResource(wavPath.toFile())); @@ -36,22 +50,26 @@ public List extractMFCC(Path wavPath) { HttpEntity> request = new HttpEntity<>(body, headers); - // FastAPI 요청 try { - String url = fastAPIBaseUrl + "/extract-mfcc"; - ResponseEntity response = restTemplate.postForEntity(url, request, List.class); + String url = fastAPIBaseUrl + endPoint; + ResponseEntity response = restTemplate.postForEntity(url, request, responseType); + + log.info("response body: {}", response.getBody()); if (!response.getStatusCode().is2xxSuccessful() || response.getBody() == null) { throw new FastapiCallFailedException(); } - @SuppressWarnings("unchecked") - List mfcc = (List) response.getBody(); - return mfcc; + return response.getBody(); - }catch (ResourceAccessException e) { + } catch (ResourceAccessException e) { throw new FastapiCallFailedException(); } } + + public record PredictResponse( + String label, + double prob + ){} } From 5a415cbc45ed28aba89e2f5b56288225fc44acb3 Mon Sep 17 00:00:00 2001 From: LgE02 Date: Sun, 21 Sep 2025 16:43:03 +0900 Subject: [PATCH 6/6] =?UTF-8?q?chore=20:=20securityFilterChain=EC=9D=98=20?= =?UTF-8?q?=EC=A0=91=EA=B7=BC=20=EA=B0=80=EB=8A=A5=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=82=B4=EC=86=8C=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20api=20=EC=82=AD=EC=A0=9C=20=ED=9B=84=20match=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/sosaw/sosaw/global/config/SecurityConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sosaw/sosaw/global/config/SecurityConfig.java b/src/main/java/com/sosaw/sosaw/global/config/SecurityConfig.java index 43ad9e2..35883ed 100644 --- a/src/main/java/com/sosaw/sosaw/global/config/SecurityConfig.java +++ b/src/main/java/com/sosaw/sosaw/global/config/SecurityConfig.java @@ -44,7 +44,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti // 인가 정책 설정 .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/users/signup", "/api/users/signin", "/api/users/signup/checkId","/api/sound/upload").permitAll() //누구나 접근 가능 + .requestMatchers("/api/users/signup", "/api/users/signin", "/api/users/signup/checkId","/api/sound/match").permitAll() //누구나 접근 가능 .requestMatchers("/api/users/profile").hasRole("ADMIN") // ADMIN 역할 가진 사용자만 접근 가능 .requestMatchers("/static/js/**","/static/css/**","/static/img/**" ,"/swagger-ui/**","/v3/api-docs/**").permitAll() // swagger