Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ public void logout(
) {
logoutService.logout(refreshToken);

// 1. 현재 표준 쿠기 (Path=/) 삭제
response.addCookie(
CookieUtils.deleteRefreshTokenCookie(cookieSecure)
);

// 2. 레거시 쿠키(Path=/api/auth)도 삭제 (과거 잔재 청소)
response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Lax"));
response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Strict"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ public OAuthLoginResponse login(
) {
OAuthLoginResult result = oAuthService.login(OAuthProvider.from(provider), code);

// 1. 레거시(/api/auth) 쿠키 제거 (예전 SameSite가 Strict였다면 Strict로 한 번 더)
response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Strict"));
response.addCookie(CookieUtils.deleteRefreshTokenCookie(cookieSecure, "/api/auth", "Lax"));

// 2. 정상(/) 쿠키 설정
response.addCookie(
CookieUtils.createRefreshTokenCookie(
result.refreshToken(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
@Builder
public class OAuthUserInfo {

private final String provider; // google / naver / kakao
private final String providerId; // OAuth 제공자 고유 ID
private final String email; // nullable 가능
private final String provider;
private final String providerId;
private final String email;
private final String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
public class OAuthToken {
private Long id;
private Long userId;
private String provider; // google / naver / kakao
private String provider;
private String accessToken;
private String refreshToken; // nullable
private LocalDateTime expiresAt; // nullable
private String refreshToken;
private LocalDateTime expiresAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,25 @@ public class AuthSessionServiceImpl implements AuthSessionService {
public MeResponse getMe(HttpServletRequest request) {
String refreshToken = extractRefreshToken(request);

// (1) refresh JWT 서명 + type 검증
Long jwtUserId;
try {
jwtUserId = jwtProvider.getUserId(refreshToken, "refresh");
} catch (JwtException | IllegalArgumentException | CustomException e) {
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// (2) DB에 저장된 토큰인지 확인
RefreshToken saved = refreshTokenMapper.findByToken(refreshToken)
.orElseThrow(() -> new CustomException(ErrorCode.REFRESH_TOKEN_INVALID));

// (3) 만료 체크
if (saved.getExpiresAt() == null || saved.getExpiresAt().isBefore(Instant.now())) {
refreshTokenMapper.deleteByToken(refreshToken);
throw new CustomException(ErrorCode.REFRESH_TOKEN_EXPIRED);
}

// (4) 토큰의 userId 일치 체크
if (!saved.getUserId().equals(jwtUserId)) {
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// (5) 유저 조회
User user = userMapper.findById(saved.getUserId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class LogoutServiceImpl implements LogoutService {
@Override
public void logout(String refreshToken) {

// refreshToken 없어도 로그아웃은 성공
if (refreshToken == null || refreshToken.isBlank()) {
log.info("Logout: no refreshToken provided");
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ public OAuthLoginResult login(OAuthProvider provider, String authorizationCode)
);
}

// 재가입이면 true, 기존이면 false
return createLoginResult(anyUser.getId(), wasDeleted, provider.value(), tokenInfo);
})
.orElseGet(() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,19 +53,15 @@ public void saveRefreshToken(Long userId, String refreshToken) {
@Override
public TokenResponse refreshAccessToken(HttpServletRequest request,
HttpServletResponse response) {

// 1. Cookie에서 refreshToken 추출
String refreshToken = extractRefreshToken(request);

// 1-1. JWT 서명 검증 (DB 조회 전)
Long jwtUserId;
try {
jwtUserId = jwtProvider.getUserId(refreshToken, "refresh");
} catch (JwtException | IllegalArgumentException | CustomException e) {
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// 2. DB 조회
RefreshToken savedToken = refreshTokenMapper.findByToken(refreshToken)
.orElseThrow(() ->
new CustomException(ErrorCode.REFRESH_TOKEN_INVALID)
Expand All @@ -75,10 +71,8 @@ public TokenResponse refreshAccessToken(HttpServletRequest request,
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// 3. 만료 체크
if (savedToken.getExpiresAt().isBefore(Instant.now())) {
// 만료된 토큰은 즉시 삭제 후 예외 반환 (rotation 로직 미진입)
refreshTokenMapper.deleteByToken(refreshToken); // 만료 토큰 정리
refreshTokenMapper.deleteByToken(refreshToken);
throw new CustomException(ErrorCode.REFRESH_TOKEN_EXPIRED);
}

Expand All @@ -88,30 +82,21 @@ public TokenResponse refreshAccessToken(HttpServletRequest request,
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// 4. 기존 refreshToken 폐기 (Rotation 로직 진입)
int deletedCount = refreshTokenMapper.deleteByToken(refreshToken);
if (deletedCount == 0) {
throw new CustomException(ErrorCode.REFRESH_TOKEN_INVALID);
}

// 5. 새 Access + Refresh 발급
TokenResponse accessToken = jwtIssuer.issueAccessToken(userId);

// 6. 새 refreshToken 발급
String newRefreshToken = jwtIssuer.issueRefreshToken(userId);

// 7. DB 저장
saveRefreshToken(userId, newRefreshToken);

// 8. 새 refreshToken 쿠키 설정
response.addCookie(CookieUtils.createRefreshTokenCookie(
newRefreshToken,
jwtProperties.refreshExpirationSeconds(),
cookieSecure
)
);

// 9. accessToken만 반환
return accessToken;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public class GoogleOAuthClient implements OAuthClient {

@Override
public OAuthAuthResult authenticate(String authorizationCode) {
Map<String, Object> token = getTokenResponse(authorizationCode); // access/refresh/expires_in
Map<String, Object> token = getTokenResponse(authorizationCode);

String accessToken = token.get("access_token").toString();
String refreshToken = token.get("refresh_token") != null ? String.valueOf(token.get("refresh_token")) : null;
Expand All @@ -48,7 +48,6 @@ public OAuthAuthResult authenticate(String authorizationCode) {
expiresIn = Long.valueOf(String.valueOf(token.get("expires_in")));
} catch (NumberFormatException e) {
log.warn("Google token response has non-numeric expires_in: {}", token.get("expires_in"));
// expires_in 파싱 실패 시 null로 처리
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public class RecordingServiceImpl implements RecordingService {
private final CounselingResultMapper sttMapper;
private final SummaryMapper summaryMapper;

//@Value 주입이 완료된 후, 호출 시점에 WebClient 빌드
private WebClient getWebClient() {
String authStr = customerId + ":" + customerSecret;
String auth = Base64.getEncoder().encodeToString(authStr.getBytes());
Expand All @@ -55,7 +54,6 @@ private WebClient getWebClient() {
@Override
public String acquire(String channelName, String uid) {
log.info("[Agora] Acquire 요청 시작 - channel: {}, uid: {}", channelName, uid);
// 전체 Body
Map<String, Object> body = Map.of(
"cname",channelName,
"uid", uid,
Expand Down Expand Up @@ -165,10 +163,10 @@ public void stop(String resourceId, String sid, String channelName, String uid,
throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
}

//s3의 파일->wav로 변경->stt
//s3 파일->wav로 변경->stt
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(10000); //TODO:차후 lambda로 호출되면 동작하도록 개선
Thread.sleep(10000); //차후 lambda로 호출되면 동작하도록 개선
String m3u8Url = String.format("https://%s.s3.ap-northeast-2.amazonaws.com/recordings/%s/%s_%s.m3u8",
s3Bucket, channelName, sid, channelName);
log.info("변환 시작: {}", m3u8Url);
Expand Down
15 changes: 5 additions & 10 deletions src/main/java/com/ureca/unity/domain/call/util/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,30 @@ public class Converter {
public File convertM3u8ToWav(String m3u8Url) {
File target=null;
try {
// 1. 결과물이 저장될 임시 파일 생성
target = File.createTempFile("recording_", ".wav");

// 2. 오디오 설정 (Google/Whisper STT 권장 사양)
// 오디오 설정
AudioAttributes audio = new AudioAttributes();
// audio.setCodec("pcm_s16le"); // WAV 표준 코덱
audio.setSamplingRate(16000); // 16kHz (STT 최적화)
audio.setSamplingRate(16000);
audio.setBitRate(256000);
audio.setChannels(1); // 모노 (화자 분리 및 인식률 향상)
audio.setChannels(1);

// 3. 인코딩 설정
EncodingAttributes attrs = new EncodingAttributes();
attrs.setOutputFormat("wav");
attrs.setAudioAttributes(audio);

// 4. 변환 실행 (URL로부터 직접 읽기)
Encoder encoder = new Encoder();
// 아고라 S3 URL을 MultimediaObject에 직접 넣습니다.
encoder.encode(new MultimediaObject(new URL(m3u8Url)), target, attrs);

log.info("WAV 변환 성공: {}", target.getAbsolutePath());
log.info("[WAV] 변환 성공: {}", target.getAbsolutePath());
return target;

} catch (Exception e) {
log.error("변환 실패: {}", m3u8Url, e);
if (target != null && target.exists() && !target.delete()) {
log.warn("임시 파일 삭제 실패: {}", target.getAbsolutePath());
}
throw new IllegalStateException("m3u8 -> wav 변환 실패", e);
throw new IllegalStateException("[WAV] m3u8 -> wav 변환 실패", e);
Comment on lines +32 to +40
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

로그 접두사가 일관되지 않습니다.

성공 로그(Line 32)와 예외 메시지(Line 40)에는 [WAV] 접두사가 있지만, 에러 로그(Line 36)와 경고 로그(Line 38)에는 접두사가 누락되었습니다. 로그 필터링과 모니터링을 위해 동일 모듈 내 로그는 일관된 접두사를 사용하는 것이 좋습니다.

🔧 일관된 접두사 적용 제안
         } catch (Exception e) {
-            log.error("변환 실패: {}", m3u8Url, e);
+            log.error("[WAV] 변환 실패: {}", m3u8Url, e);
             if (target != null && target.exists() && !target.delete()) {
-                log.warn("임시 파일 삭제 실패: {}", target.getAbsolutePath());
+                log.warn("[WAV] 임시 파일 삭제 실패: {}", target.getAbsolutePath());
             }
             throw new IllegalStateException("[WAV] m3u8 -> wav 변환 실패", e);
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
log.info("[WAV] 변환 성공: {}", target.getAbsolutePath());
return target;
} catch (Exception e) {
log.error("변환 실패: {}", m3u8Url, e);
if (target != null && target.exists() && !target.delete()) {
log.warn("임시 파일 삭제 실패: {}", target.getAbsolutePath());
}
throw new IllegalStateException("m3u8 -> wav 변환 실패", e);
throw new IllegalStateException("[WAV] m3u8 -> wav 변환 실패", e);
log.info("[WAV] 변환 성공: {}", target.getAbsolutePath());
return target;
} catch (Exception e) {
log.error("[WAV] 변환 실패: {}", m3u8Url, e);
if (target != null && target.exists() && !target.delete()) {
log.warn("[WAV] 임시 파일 삭제 실패: {}", target.getAbsolutePath());
}
throw new IllegalStateException("[WAV] m3u8 -> wav 변환 실패", e);
🤖 Prompt for AI Agents
In `@src/main/java/com/ureca/unity/domain/call/util/Converter.java` around lines
32 - 40, The log messages in Converter.java are inconsistent: add the same
"[WAV]" prefix to the existing log.error and log.warn calls so all logs from
this conversion block match the log.info prefix; specifically update the
log.error("변환 실패: {}", m3u8Url, e) and log.warn("임시 파일 삭제 실패: {}",
target.getAbsolutePath()) invocations to include "[WAV]" in their messages (keep
the exception e and variables m3u8Url/target unchanged) so filtering/monitoring
is consistent across the conversion flow.

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ public RecommendResponse get(@PathVariable long summaryId) {
// 전체 추천 조회
@GetMapping("/me")
public RecommendResponse getAll() {
// undefined 'currentUserId' 제거 — 서비스의 카테고리 기반 조회 재사용
return recommendService.getRandomByCategory();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,30 +107,4 @@ public RecommendResponse getRandomByCategory() {
response.setItems(results);
return response;
}


// public List<RecommendItem> getRecommendationsWithFallback(long summaryId) {
// List<RecommendItem> items = recommendMapper.selectBySummaryId(summaryId);
//
// // 추천 없으면 빈 리스트 처리
// if (items == null) {
// items = new ArrayList<>();
// }
//
// // 카테고리별로 그룹화
// Map<Integer, List<RecommendItem>> byCategory = items.stream()
// .collect(Collectors.groupingBy(RecommendItem::getCategoryId));
//
// List<Category> categories = categoryMapper.selectAll();
// for (Category c : categories) {
// if (!byCategory.containsKey(c.getCategoryId()) || byCategory.get(c.getCategoryId()).isEmpty()) {
// // 추천 없으면 fallback
// List<RecommendItem> fallback = recommendMapper.selectRandomByCategory(c.getCategoryId());
// items.addAll(fallback);
// }
// }
//
// // score=0, rankNo=0인 fallback도 이미 Mapper에서 설정됨
// return items;
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,12 @@ public class SttServiceImpl implements SttService {
@Override
public CounselingResult startStt(File file, long userId, CounselingResult job) {
String gcsUri = null;

try {
if (!file.exists() || file.length() == 0) {
throw new RuntimeException("오디오 파일이 존재하지 않거나 비어있습니다.");
}
log.info("[STT] 시작 - file: {}, size: {} bytes", file.getAbsolutePath(), file.length());

log.info("STT 시작 (Long Audio) - file: {}, size: {} bytes",
file.getAbsolutePath(), file.length());

// 1️⃣ GCS 업로드
String objectName = "recordings/"
+ userId + "/"
+ job.getCounselingResultId() + ".wav";
Expand All @@ -53,26 +49,21 @@ public CounselingResult startStt(File file, long userId, CounselingResult job) {
objectName,
file.toPath()
);
log.info("[GCS] 업로드 완료: {}", gcsUri);

log.info("GCS 업로드 완료: {}", gcsUri);

// 2️⃣ RecognitionAudio (URI 기반)
RecognitionAudio audio = RecognitionAudio.newBuilder()
.setUri(gcsUri)
.build();

// 3️⃣ Long Audio 최적화 설정
RecognitionConfig config = RecognitionConfig.newBuilder()
.setLanguageCode("ko-KR")
.setEncoding(RecognitionConfig.AudioEncoding.ENCODING_UNSPECIFIED)
// .setSampleRateHertz(16000) // wav 실제 값과 반드시 일치
.setAudioChannelCount(1)
.setEnableAutomaticPunctuation(true)
.setUseEnhanced(true)
.setModel("latest_long")
.build();

// 4️⃣ Google 인증
Resource resource = new DefaultResourceLoader().getResource(keyPath);
try (InputStream is = resource.getInputStream()) {

Expand All @@ -92,7 +83,7 @@ public CounselingResult startStt(File file, long userId, CounselingResult job) {
> future =
speechClient.longRunningRecognizeAsync(config, audio);

// ⏳ 긴 파일 대기 (최대 30분)
//대기 최대 30분
LongRunningRecognizeResponse response =
future.get(30, TimeUnit.MINUTES);

Expand All @@ -101,34 +92,30 @@ public CounselingResult startStt(File file, long userId, CounselingResult job) {
.collect(Collectors.joining(" "));

if (text.isBlank()) {
log.warn("STT 결과가 비어있음");
log.warn("[STT] 결과가 비어있음");
job.setStatus("FAIL");
job.setTexts("No speech detected.");
} else {
log.info("STT 완료 (length={} chars)", text.length());
log.info("[STT] 완료 (length={} chars)", text.length());
job.setStatus("SUCCESS");
job.setTexts(text);
}
}
}

} catch (Exception e) {
log.error("STT 처리 중 오류 발생", e);
log.error("[STT] 처리 중 오류 발생", e);
job.setStatus("FAIL");
job.setTexts("Error: " + e.getMessage());

} finally {
// 5️⃣ 로컬 파일 삭제
if (file.exists()) {
boolean deleted = file.delete();
log.info("로컬 wav 파일 삭제: {}", deleted);
log.info("[WAV] 로컬 wav 파일 삭제: {}", deleted);
}
}

// 6️⃣ DB 업데이트
sttMapper.updateResult(job);

// 7️⃣ 성공 시 요약 호출
if ("SUCCESS".equals(job.getStatus())) {
summaryService.createSummary(
job.getCounselingResultId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,7 @@ public void createSummary(Long counselingResultId, Long userId, String counselin

summaryMapper.updateStatus(summaryId, "SUCCESS");

// (현재는 반환값 사용 안 하니 생성만 유지)
new SummaryResponse(gemini.getTitle(), gemini.getSubject(), keywords, points);

} catch (Exception e) {
summaryMapper.updateStatus(summaryId, "FAIL");
throw new IllegalStateException(e);
Expand Down
Loading
Loading