Skip to content

Commit 7cccddd

Browse files
authored
Merge pull request #26 from EyeStock/feat/#24-stt-coin-predict
feat: stt 이용해 코인 종목명 입력 및 FASTAPI 호출
2 parents edd24f9 + 289f270 commit 7cccddd

12 files changed

Lines changed: 285 additions & 66 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ jobs:
7474
echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env
7575
echo "ACCESS_EXPIRATION=${{ secrets.ACCESS_EXPIRATION }}" >> .env
7676
echo "REFRESH_EXPIRATION=${{ secrets.REFRESH_EXPIRATION }}" >> .env
77+
echo "GCP_STT_KEY=${{ secrets.GCP_STT_KEY }}" >> .env
7778
7879
cd ~/app
7980
sudo docker rm -f app-server || true
8081
sudo docker compose down || true
8182
sudo docker image rm ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} || true
8283
sudo docker compose pull
83-
sudo docker compose up -d
84+
sudo docker compose up -d

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ FROM amazoncorretto:17
33
ARG JAR_FILE=build/libs/*.jar
44
COPY ${JAR_FILE} app.jar
55

6-
ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "/app.jar"]
6+
ENTRYPOINT ["java", "-jar", "/app.jar"]

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ dependencies {
5454
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
5555
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
5656
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
57+
58+
// stt
59+
implementation 'com.google.cloud:google-cloud-speech:4.72.0'
5760
}
5861

5962
tasks.named('test') {

docker-compose.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,8 @@ services:
77
- "8080:8080"
88
environment:
99
- SPRING_PROFILES_ACTIVE=prod
10-
- JWT_SECRET=${JWT_SECRET}
11-
- ACCESS_EXPIRATION=${ACCESS_EXPIRATION}
12-
- REFRESH_EXPIRATION=${REFRESH_EXPIRATION}
1310
redis:
1411
image: redis:7.2
1512
container_name: redis-server
1613
ports:
17-
- "6379:6379"
14+
- "6379:6379"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.api.sss.config;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.IOException;
5+
import java.util.Base64;
6+
7+
import org.springframework.beans.factory.annotation.Value;
8+
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Configuration;
10+
11+
import com.google.auth.oauth2.GoogleCredentials;
12+
import com.google.cloud.speech.v1.SpeechClient;
13+
import com.google.cloud.speech.v1.SpeechSettings;
14+
15+
@Configuration
16+
public class GoogleCloudConfig {
17+
18+
private final GoogleCredentials googleCredentials;
19+
20+
public GoogleCloudConfig(
21+
@Value("${spring.cloud.gcp.credentials.encoded-key}")
22+
String encodedKey
23+
) throws IOException {
24+
byte[] decoded = Base64.getDecoder().decode(encodedKey);
25+
this.googleCredentials =
26+
GoogleCredentials.fromStream(new ByteArrayInputStream(decoded));
27+
}
28+
29+
@Bean
30+
public SpeechSettings speechSettings() throws IOException {
31+
return SpeechSettings.newBuilder()
32+
.setCredentialsProvider(() -> googleCredentials)
33+
.build();
34+
}
35+
36+
@Bean
37+
public SpeechClient speechClient(SpeechSettings speechSettings) throws IOException {
38+
return SpeechClient.create(speechSettings);
39+
}
40+
}

src/main/java/com/api/sss/config/exception/ErrorCode.java

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@
66
@Getter
77
@AllArgsConstructor
88
public enum ErrorCode {
9-
TEST_ERROR_CODE(400, "응답 테스트 실패입니다."),
10-
DEVICE_ALREADY_REGISTERED(400, "이미 등록된 디바이스입니다."),
11-
MEMBER_NOT_FOUND(404, "해당 사용자를 찾을 수 없습니다."),
12-
INVALID_CHALLENGE(401, "challenge 값이 유효하지 않습니다."),
13-
INVALID_SIGNATURE(401, "서명 검증에 실패했습니다."),
14-
EXPIRED_TOKEN(401, "토큰이 만료되었습니다."),
15-
INVALID_TOKEN(401, "유효하지 않은 토큰입니다."),
16-
INVALID_REFRESH_TOKEN(401, "Refresh Token이 유효하지 않습니다."),
17-
UNSUPPORTED_TOKEN(401,"지원하지 않는 토큰입니다."),
18-
FASTAPI_COMMUNICATION_ERROR(500, "FastAPI와의 통신 중 오류가 발생했습니다."),
9+
TEST_ERROR_CODE(400, "응답 테스트 실패입니다."),
10+
DEVICE_ALREADY_REGISTERED(400, "이미 등록된 디바이스입니다."),
11+
MEMBER_NOT_FOUND(404, "해당 사용자를 찾을 수 없습니다."),
12+
INVALID_CHALLENGE(401, "challenge 값이 유효하지 않습니다."),
13+
INVALID_SIGNATURE(401, "서명 검증에 실패했습니다."),
14+
EXPIRED_TOKEN(401, "토큰이 만료되었습니다."),
15+
INVALID_TOKEN(401, "유효하지 않은 토큰입니다."),
16+
INVALID_REFRESH_TOKEN(401, "Refresh Token이 유효하지 않습니다."),
17+
UNSUPPORTED_TOKEN(401, "지원하지 않는 토큰입니다."),
18+
FASTAPI_COMMUNICATION_ERROR(500, "FastAPI와의 통신 중 오류가 발생했습니다."),
19+
FILE_NOT_FOUND(400, "입력 파일이 없습니다."),
20+
FILE_READ_ERROR(500, "서버에서 파일을 읽어들이는 중 오류가 발생했습니다.");
1921

20-
;
21-
22-
private final int code;
23-
private final String message;
22+
private final int code;
23+
private final String message;
2424
}

src/main/java/com/api/sss/login/dto/request/BiometricLoginVerifyRequest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.api.sss.login.dto.request;
22

3+
import io.swagger.v3.oas.annotations.media.Schema;
34
import jakarta.validation.constraints.NotBlank;
45
import lombok.Getter;
56

67
@Getter
78
public class BiometricLoginVerifyRequest {
89

910
@NotBlank(message = "deviceId는 필수입니다.")
11+
@Schema(description = "기기 고유 ID", example = "abc123device")
1012
private String deviceId;
1113

1214
@NotBlank(message = "challenge는 필수입니다.")
Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,87 @@
11
package com.api.sss.model.service;
22

3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
import org.springframework.http.HttpEntity;
8+
import org.springframework.http.HttpHeaders;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.web.client.RestTemplate;
14+
315
import com.api.sss.config.exception.CustomException;
416
import com.api.sss.config.exception.ErrorCode;
517
import com.api.sss.model.dto.request.ChatAskRequest;
6-
import com.api.sss.model.dto.response.ChatAskResponse;
718
import com.api.sss.model.dto.request.NewsRequest;
19+
import com.api.sss.model.dto.response.ChatAskResponse;
820
import com.api.sss.model.dto.response.NewsResponse;
9-
import org.springframework.http.*;
10-
import org.springframework.stereotype.Service;
11-
import org.springframework.web.client.RestTemplate;
12-
13-
import java.util.List;
1421

1522
@Service
1623
public class ModelService {
1724

18-
private final RestTemplate restTemplate = new RestTemplate();
19-
private final String FASTAPI_URL = "http://203.153.147.12:5050";
20-
21-
public List<NewsResponse.Result> cardNews(NewsRequest request) {
22-
HttpHeaders headers = new HttpHeaders();
23-
headers.setContentType(MediaType.APPLICATION_JSON);
24-
25-
HttpEntity<NewsRequest> entity = new HttpEntity<>(request, headers);
26-
27-
try {
28-
ResponseEntity<NewsResponse> response = restTemplate.exchange(
29-
FASTAPI_URL + "/news",
30-
HttpMethod.POST,
31-
entity,
32-
NewsResponse.class
33-
);
34-
return response.getBody().getResults();
35-
} catch (Exception e) {
36-
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
37-
}
38-
}
39-
40-
public ChatAskResponse askQuestion(ChatAskRequest request) {
41-
HttpHeaders headers = new HttpHeaders();
42-
headers.setContentType(MediaType.APPLICATION_JSON);
43-
44-
HttpEntity<ChatAskRequest> entity = new HttpEntity<>(request, headers);
45-
46-
try {
47-
ResponseEntity<ChatAskResponse> response = restTemplate.exchange(
48-
FASTAPI_URL + "/chat",
49-
HttpMethod.POST,
50-
entity,
51-
ChatAskResponse.class
52-
);
53-
return response.getBody();
54-
} catch (Exception e) {
55-
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
56-
}
57-
}
25+
private final RestTemplate restTemplate = new RestTemplate();
26+
private final String FASTAPI_URL = "http://203.153.147.12:5050";
27+
28+
public List<NewsResponse.Result> cardNews(NewsRequest request) {
29+
HttpHeaders headers = new HttpHeaders();
30+
headers.setContentType(MediaType.APPLICATION_JSON);
31+
32+
HttpEntity<NewsRequest> entity = new HttpEntity<>(request, headers);
33+
34+
try {
35+
ResponseEntity<NewsResponse> response = restTemplate.exchange(
36+
FASTAPI_URL + "/news",
37+
HttpMethod.POST,
38+
entity,
39+
NewsResponse.class
40+
);
41+
return response.getBody().getResults();
42+
} catch (Exception e) {
43+
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
44+
}
45+
}
46+
47+
public ChatAskResponse askQuestion(ChatAskRequest request) {
48+
HttpHeaders headers = new HttpHeaders();
49+
headers.setContentType(MediaType.APPLICATION_JSON);
50+
51+
HttpEntity<ChatAskRequest> entity = new HttpEntity<>(request, headers);
52+
53+
try {
54+
ResponseEntity<ChatAskResponse> response = restTemplate.exchange(
55+
FASTAPI_URL + "/chat",
56+
HttpMethod.POST,
57+
entity,
58+
ChatAskResponse.class
59+
);
60+
return response.getBody();
61+
} catch (Exception e) {
62+
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
63+
}
64+
}
65+
66+
public String predictCoin(String coinName) {
67+
HttpHeaders headers = new HttpHeaders();
68+
headers.setContentType(MediaType.APPLICATION_JSON);
69+
70+
Map<String, String> body = new HashMap<>();
71+
body.put("coinName", coinName);
72+
HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);
73+
74+
try {
75+
// TODO: return response.getBody();
76+
// ResponseEntity<String> response = restTemplate.exchange(
77+
// FASTAPI_URL + "/chat/predict",
78+
// HttpMethod.POST,
79+
// entity,
80+
// String.class
81+
// );
82+
return coinName;
83+
} catch (Exception e) {
84+
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
85+
}
86+
}
5887
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.api.sss.stt.controller;
2+
3+
import org.springframework.http.MediaType;
4+
import org.springframework.web.bind.annotation.PostMapping;
5+
import org.springframework.web.bind.annotation.RequestMapping;
6+
import org.springframework.web.bind.annotation.RequestParam;
7+
import org.springframework.web.bind.annotation.RestController;
8+
import org.springframework.web.multipart.MultipartFile;
9+
10+
import com.api.sss.config.response.dto.CustomResponse;
11+
import com.api.sss.config.response.dto.SuccessStatus;
12+
import com.api.sss.stt.dto.response.ChatPredictResponse;
13+
import com.api.sss.stt.service.SttService;
14+
15+
import io.swagger.v3.oas.annotations.Operation;
16+
import io.swagger.v3.oas.annotations.Parameter;
17+
import io.swagger.v3.oas.annotations.media.Content;
18+
import io.swagger.v3.oas.annotations.media.ExampleObject;
19+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
20+
import lombok.RequiredArgsConstructor;
21+
22+
@RestController
23+
@RequiredArgsConstructor
24+
@RequestMapping("/api/v1")
25+
public class SttController {
26+
27+
private final SttService sttService;
28+
29+
@Operation(
30+
summary = "코인 시세 예측 API",
31+
description = "STT로 코인종목 명을 입력받아 FastAPI 서비스에 전달하고 답변을 받아옵니다."
32+
)
33+
@ApiResponse(responseCode = "200", description = "답변 수신 성공")
34+
@ApiResponse(
35+
responseCode = "500",
36+
description = "FastAPI 서비스 오류 또는 통신 실패",
37+
content = @Content(mediaType = "application/json", examples = @ExampleObject(value = """
38+
{
39+
"code": 500,
40+
"message": "FastAPI와의 통신 중 오류가 발생했습니다."
41+
}
42+
"""))
43+
// 종목명 인식 실패했을 시 response 추가 필요
44+
)
45+
@PostMapping(path = "/chat/predict", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
46+
public CustomResponse<ChatPredictResponse> predictCoin(
47+
@Parameter(
48+
description = "업로드할 44100Hz mp3 음성 파일",
49+
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE)
50+
)
51+
@RequestParam("file") MultipartFile file) {
52+
ChatPredictResponse response = sttService.predictCoin(file);
53+
return CustomResponse.success(response, SuccessStatus.SUCCESS);
54+
}
55+
56+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.api.sss.stt.dto.response;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
@AllArgsConstructor
10+
public class ChatPredictResponse {
11+
private String prediction;
12+
}

0 commit comments

Comments
 (0)