Skip to content

Commit e5350b3

Browse files
authored
Merge branch 'develop' into URECA-48/Feat/gemini
2 parents 0bf3054 + 666a143 commit e5350b3

10 files changed

Lines changed: 303 additions & 11 deletions

File tree

‎build.gradle‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
plugins {
2-
id 'java'
3-
// id 'org.springframework.boot' version '4.0.1'
4-
id 'org.springframework.boot' version '3.2.5'
5-
// id 'io.spring.dependency-management' version '1.1.7'
6-
id 'io.spring.dependency-management' version '1.1.4'
7-
}
2+
id 'java'
3+
id 'org.springframework.boot' version '3.2.5'
4+
id 'io.spring.dependency-management' version '1.1.4'
5+
}
86

97
group = 'com.ureca'
108
version = '0.0.1-SNAPSHOT'
@@ -29,10 +27,9 @@ repositories {
2927
dependencies {
3028
implementation 'org.springframework.boot:spring-boot-starter-validation'
3129
implementation 'org.springframework.boot:spring-boot-starter-security'
32-
// implementation 'org.springframework.boot:spring-boot-starter-webmvc'
3330
implementation 'org.springframework.boot:spring-boot-starter-web'
34-
// implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:4.0.1'
3531
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
32+
implementation 'com.google.cloud:google-cloud-speech:4.46.0'
3633
compileOnly 'org.projectlombok:lombok'
3734
runtimeOnly 'com.mysql:mysql-connector-j'
3835
annotationProcessor 'org.projectlombok:lombok'
@@ -45,6 +42,8 @@ dependencies {
4542
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
4643
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0'
4744
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0'
45+
46+
implementation 'ws.schild:jave-all-deps:3.3.1'
4847
}
4948

5049
tasks.named('test') {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.ureca.unity.domain.stt.controller;
2+
3+
import com.ureca.unity.domain.stt.model.SttJob;
4+
import com.ureca.unity.domain.stt.service.SttService;
5+
import lombok.RequiredArgsConstructor;
6+
import org.springframework.web.bind.annotation.*;
7+
8+
@RestController
9+
@RequiredArgsConstructor
10+
@RequestMapping("/counselings")
11+
public class SttController {
12+
13+
private final SttService sttService;
14+
15+
@PostMapping("/{counselingId}/stt")
16+
public SttJob start(@PathVariable Long counselingId) {
17+
return sttService.startStt(counselingId);
18+
}
19+
20+
@GetMapping("/{counselingId}/stt")
21+
public SttJob get(@PathVariable Long counselingId) {
22+
return sttService.getStt(counselingId);
23+
}
24+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.ureca.unity.domain.stt.mapper;
2+
3+
import com.ureca.unity.domain.stt.model.SttJob;
4+
import org.apache.ibatis.annotations.Mapper;
5+
6+
@Mapper
7+
public interface SttMapper {
8+
9+
void insert(SttJob sttJob);
10+
11+
void updateResult(SttJob sttJob);
12+
13+
SttJob findByCounselingId(Long counselingId);
14+
15+
SttJob findById(Long sttJobId);
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.ureca.unity.domain.stt.model;
2+
3+
import lombok.*;
4+
5+
import java.time.LocalDateTime;
6+
7+
@Getter
8+
@Setter
9+
@NoArgsConstructor
10+
@AllArgsConstructor
11+
@Builder
12+
public class SttJob {
13+
14+
private Long sttJobId;
15+
private Long counselingId;
16+
private Long userId;
17+
18+
private String summaryType; // CALL / KEYWORD
19+
private String status; // LOADING(PROGRESS) / SUCCESS / FAIL
20+
21+
private String summaryText; // STT or LLM result
22+
private String keywords; // JSON 문자열로 처리 (임시)
23+
24+
private LocalDateTime createdAt;
25+
private LocalDateTime updatedAt;
26+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.ureca.unity.domain.stt.response;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public class SttResponse {
9+
10+
private Long id;
11+
private String name;
12+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.ureca.unity.domain.stt.service;
2+
3+
import com.ureca.unity.domain.stt.model.SttJob;
4+
5+
public interface SttService {
6+
7+
SttJob startStt(Long counselingId);
8+
9+
SttJob getStt(Long counselingId);
10+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.ureca.unity.domain.stt.service;
2+
3+
import com.google.api.gax.core.FixedCredentialsProvider;
4+
import com.google.auth.oauth2.GoogleCredentials;
5+
import com.google.cloud.speech.v1.*;
6+
import com.google.protobuf.ByteString;
7+
import com.ureca.unity.domain.stt.mapper.SttMapper;
8+
import com.ureca.unity.domain.stt.model.SttJob;
9+
import lombok.RequiredArgsConstructor;
10+
import lombok.extern.slf4j.Slf4j;
11+
import org.springframework.beans.factory.annotation.Value;
12+
import org.springframework.core.io.DefaultResourceLoader;
13+
import org.springframework.core.io.Resource;
14+
import org.springframework.stereotype.Service;
15+
import ws.schild.jave.Encoder;
16+
import ws.schild.jave.MultimediaObject;
17+
import ws.schild.jave.encode.AudioAttributes;
18+
import ws.schild.jave.encode.EncodingAttributes;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.nio.file.Paths;
26+
import java.util.stream.Collectors;
27+
28+
@Service
29+
@RequiredArgsConstructor
30+
@Slf4j
31+
public class SttServiceImpl implements SttService {
32+
33+
private final SttMapper sttMapper;
34+
35+
@Value("${google.cloud.credentials.location}")
36+
private String keyPath;
37+
38+
@Override
39+
public SttJob startStt(Long counselingId) {
40+
Long userId = 1L; // 임시
41+
42+
SttJob job = sttMapper.findByCounselingId(counselingId);
43+
44+
if (job == null) {
45+
job = SttJob.builder()
46+
.counselingId(counselingId)
47+
.userId(userId)
48+
.summaryType("CALL")
49+
.status("LOADING")
50+
.build();
51+
sttMapper.insert(job);
52+
}
53+
54+
// 1. 오디오 파일 읽기
55+
// String audioPath = "C:/Users/food0/Desktop/realsample.wav";
56+
Path m4aPath = Paths.get("C:/Users/food0/Desktop/테헤란로.m4a");
57+
File tempWavFile = null;
58+
59+
try {
60+
tempWavFile = convertM4aToWav(m4aPath.toFile());
61+
62+
byte[] data = Files.readAllBytes(tempWavFile.toPath());
63+
ByteString audioBytes = ByteString.copyFrom(data);
64+
65+
RecognitionAudio audio = RecognitionAudio.newBuilder()
66+
.setContent(audioBytes)
67+
.build();
68+
69+
RecognitionConfig config = RecognitionConfig.newBuilder()
70+
.setEncoding(RecognitionConfig.AudioEncoding.LINEAR16)
71+
.setLanguageCode("ko-KR")
72+
.setSampleRateHertz(16000)
73+
.setAudioChannelCount(1)
74+
.build();
75+
76+
// 2. Google STT 인증 및 실행
77+
Resource resource = new DefaultResourceLoader().getResource(keyPath);
78+
try (InputStream is = resource.getInputStream()) {
79+
GoogleCredentials credentials = GoogleCredentials.fromStream(is);
80+
SpeechSettings settings = SpeechSettings.newBuilder()
81+
.setCredentialsProvider(FixedCredentialsProvider.create(credentials))
82+
.build();
83+
84+
try (SpeechClient speechClient = SpeechClient.create(settings)) {
85+
RecognizeResponse response = speechClient.recognize(config, audio);
86+
87+
String text = response.getResultsList().stream()
88+
.map(r -> r.getAlternatives(0).getTranscript())
89+
.collect(Collectors.joining(" "));
90+
91+
job.setStatus("SUCCESS");
92+
job.setSummaryText(text);
93+
}
94+
} // InputStream close
95+
96+
} catch (Exception e) {
97+
log.error("STT 처리 중 오류 발생: ", e);
98+
job.setStatus("FAIL"); // 에러 시 상태 업데이트
99+
job.setSummaryText("Error: " + e.getMessage());
100+
} finally {
101+
// 4. 사용 후 임시 wav 파일 삭제 (서버 용량 관리)
102+
if (tempWavFile != null && tempWavFile.exists()) {
103+
tempWavFile.delete();
104+
log.info("임시 변환 파일 삭제 완료");
105+
}
106+
}
107+
108+
// 3. 결과 DB 업데이트
109+
sttMapper.updateResult(job);
110+
return job;
111+
}
112+
113+
private File convertM4aToWav(File source) throws Exception {
114+
File target = File.createTempFile("stt_temp_", ".wav");
115+
116+
AudioAttributes audio = new AudioAttributes();
117+
audio.setCodec("pcm_s16le"); // Google STT 권장 코덱
118+
audio.setSamplingRate(16000);
119+
audio.setChannels(1);
120+
121+
EncodingAttributes attrs = new EncodingAttributes();
122+
attrs.setOutputFormat("wav");
123+
attrs.setAudioAttributes(audio);
124+
125+
Encoder encoder = new Encoder();
126+
encoder.encode(new MultimediaObject(source), target, attrs);
127+
128+
log.info("변환 완료: {} -> {}", source.getName(), target.getName());
129+
return target;
130+
}
131+
132+
@Override
133+
public SttJob getStt(Long counselingId) {
134+
return sttMapper.findByCounselingId(counselingId);
135+
}
136+
}

‎src/main/java/com/ureca/unity/global/config/SecurityConfig.java‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public class SecurityConfig {
1717
private final JwtProvider jwtProvider;
1818
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
1919

20-
2120
@Bean
2221
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
2322

@@ -28,8 +27,15 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
2827
"/v3/api-docs/**",
2928
"/swagger-ui.html",
3029
"/api/auth/login/**",
31-
"/api/auth/refresh")
32-
.permitAll()
30+
"/api/auth/refresh",
31+
32+
// 🔴 여기 있던 잘못된 것 삭제
33+
// "/counselings/**/stt"
34+
35+
// ✅ 이 줄로 교체
36+
"/counselings/*/stt"
37+
).permitAll()
38+
3339
.requestMatchers("/api/auth/logout")
3440
.authenticated()
3541
.anyRequest().authenticated()
@@ -44,4 +50,5 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
4450

4551
return http.build();
4652
}
53+
4754
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<!DOCTYPE mapper
3+
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4+
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5+
6+
<mapper namespace="com.ureca.unity.domain.stt.mapper.SttMapper">
7+
8+
<resultMap id="sttJobMap" type="com.ureca.unity.domain.stt.model.SttJob">
9+
<id property="sttJobId" column="stt_job_id"/>
10+
<result property="counselingId" column="counseling_id"/>
11+
<result property="userId" column="user_id"/>
12+
<result property="summaryType" column="summary_type"/>
13+
<result property="status" column="status"/>
14+
<result property="summaryText" column="summary_text"/>
15+
<result property="keywords" column="keywords"/>
16+
<result property="createdAt" column="created_at"/>
17+
<result property="updatedAt" column="updated_at"/>
18+
</resultMap>
19+
20+
<select id="findByCounselingId" resultMap="sttJobMap">
21+
SELECT *
22+
FROM stt_jobs
23+
WHERE counseling_id = #{counselingId}
24+
</select>
25+
26+
<select id="findById" resultMap="sttJobMap">
27+
SELECT *
28+
FROM stt_jobs
29+
WHERE stt_job_id = #{sttJobId}
30+
</select>
31+
32+
<insert id="insert"
33+
parameterType="com.ureca.unity.domain.stt.model.SttJob"
34+
useGeneratedKeys="true"
35+
keyProperty="sttJobId">
36+
INSERT INTO stt_jobs (
37+
counseling_id,
38+
user_id,
39+
summary_type,
40+
status
41+
)
42+
VALUES (
43+
#{counselingId},
44+
#{userId},
45+
#{summaryType},
46+
#{status}
47+
)
48+
</insert>
49+
50+
<update id="updateResult"
51+
parameterType="com.ureca.unity.domain.stt.model.SttJob">
52+
UPDATE stt_jobs
53+
SET
54+
status = #{status},
55+
summary_text = #{summaryText},
56+
updated_at = NOW()
57+
WHERE stt_job_id = #{sttJobId}
58+
</update>
59+
60+
</mapper>

‎tash list‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
stash@{0}: WIP on URECA-41/Feat/bookmark: d85b0f6 [URECA-29] Feat: JWT 기반 AccessToken/RefreshToken 발급/재발급 및 로그아웃 구현 (#17)
2+
stash@{1}: WIP on main: aa37cf2 fix: issue 양식 수정

0 commit comments

Comments
 (0)