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 + } } 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/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/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); - } -} 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 ad9058e..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,9 +1,11 @@ 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; +import java.util.List; import java.util.Optional; @Repository @@ -15,4 +17,7 @@ public interface SoundSettingRepository extends JpaRepository findByUserUserIdAndCustomSoundId(Long userId, Long customId); Optional findByUserUserIdAndBasicSoundId(Long userId, Long basicId); + List findByUserUserId(Long userId); + + Optional findByUserUserIdAndBasicSound(Long userId, BasicSound basicSound); } 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..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,18 +2,23 @@ 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; 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 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; +import java.util.List; + @Service @RequiredArgsConstructor public class SoundSettingServiceImpl implements SoundSettingService { @@ -21,6 +26,40 @@ 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()); + + //기본 사운드 설정 조회 + 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 @@ -46,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 @@ -66,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 @@ -84,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 @@ -102,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 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() + ); + } +} 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 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 + ){} }