Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ jobs:
echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env
echo "ACCESS_EXPIRATION=${{ secrets.ACCESS_EXPIRATION }}" >> .env
echo "REFRESH_EXPIRATION=${{ secrets.REFRESH_EXPIRATION }}" >> .env
echo "GCP_STT_KEY=${{ secrets.GCP_STT_KEY }}" >> .env

cd ~/app
sudo docker rm -f app-server || true
sudo docker compose down || true
sudo docker image rm ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} || true
sudo docker compose pull
sudo docker compose up -d
sudo docker compose up -d
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ FROM amazoncorretto:17
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "/app.jar"]
ENTRYPOINT ["java", "-jar", "/app.jar"]
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// stt
implementation 'com.google.cloud:google-cloud-speech:4.72.0'
}

tasks.named('test') {
Expand Down
5 changes: 1 addition & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ services:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- JWT_SECRET=${JWT_SECRET}
- ACCESS_EXPIRATION=${ACCESS_EXPIRATION}
- REFRESH_EXPIRATION=${REFRESH_EXPIRATION}
redis:
image: redis:7.2
container_name: redis-server
ports:
- "6379:6379"
- "6379:6379"
40 changes: 40 additions & 0 deletions src/main/java/com/api/sss/config/GoogleCloudConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.api.sss.config;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Base64;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.speech.v1.SpeechClient;
import com.google.cloud.speech.v1.SpeechSettings;

@Configuration
public class GoogleCloudConfig {

private final GoogleCredentials googleCredentials;

public GoogleCloudConfig(
@Value("${spring.cloud.gcp.credentials.encoded-key}")
String encodedKey
) throws IOException {
byte[] decoded = Base64.getDecoder().decode(encodedKey);
this.googleCredentials =
GoogleCredentials.fromStream(new ByteArrayInputStream(decoded));
}

@Bean
public SpeechSettings speechSettings() throws IOException {
return SpeechSettings.newBuilder()
.setCredentialsProvider(() -> googleCredentials)
.build();
}

@Bean
public SpeechClient speechClient(SpeechSettings speechSettings) throws IOException {
return SpeechClient.create(speechSettings);
}
}
28 changes: 14 additions & 14 deletions src/main/java/com/api/sss/config/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
@Getter
@AllArgsConstructor
public enum ErrorCode {
TEST_ERROR_CODE(400, "응답 테스트 실패입니다."),
DEVICE_ALREADY_REGISTERED(400, "이미 등록된 디바이스입니다."),
MEMBER_NOT_FOUND(404, "해당 사용자를 찾을 수 없습니다."),
INVALID_CHALLENGE(401, "challenge 값이 유효하지 않습니다."),
INVALID_SIGNATURE(401, "서명 검증에 실패했습니다."),
EXPIRED_TOKEN(401, "토큰이 만료되었습니다."),
INVALID_TOKEN(401, "유효하지 않은 토큰입니다."),
INVALID_REFRESH_TOKEN(401, "Refresh Token이 유효하지 않습니다."),
UNSUPPORTED_TOKEN(401,"지원하지 않는 토큰입니다."),
FASTAPI_COMMUNICATION_ERROR(500, "FastAPI와의 통신 중 오류가 발생했습니다."),
TEST_ERROR_CODE(400, "응답 테스트 실패입니다."),
DEVICE_ALREADY_REGISTERED(400, "이미 등록된 디바이스입니다."),
MEMBER_NOT_FOUND(404, "해당 사용자를 찾을 수 없습니다."),
INVALID_CHALLENGE(401, "challenge 값이 유효하지 않습니다."),
INVALID_SIGNATURE(401, "서명 검증에 실패했습니다."),
EXPIRED_TOKEN(401, "토큰이 만료되었습니다."),
INVALID_TOKEN(401, "유효하지 않은 토큰입니다."),
INVALID_REFRESH_TOKEN(401, "Refresh Token이 유효하지 않습니다."),
UNSUPPORTED_TOKEN(401, "지원하지 않는 토큰입니다."),
FASTAPI_COMMUNICATION_ERROR(500, "FastAPI와의 통신 중 오류가 발생했습니다."),
FILE_NOT_FOUND(400, "입력 파일이 없습니다."),
FILE_READ_ERROR(500, "서버에서 파일을 읽어들이는 중 오류가 발생했습니다.");

;

private final int code;
private final String message;
private final int code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.api.sss.login.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;

@Getter
public class BiometricLoginVerifyRequest {

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

@NotBlank(message = "challenge는 필수입니다.")
Expand Down
121 changes: 75 additions & 46 deletions src/main/java/com/api/sss/model/service/ModelService.java
Original file line number Diff line number Diff line change
@@ -1,58 +1,87 @@
package com.api.sss.model.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import com.api.sss.config.exception.CustomException;
import com.api.sss.config.exception.ErrorCode;
import com.api.sss.model.dto.request.ChatAskRequest;
import com.api.sss.model.dto.response.ChatAskResponse;
import com.api.sss.model.dto.request.NewsRequest;
import com.api.sss.model.dto.response.ChatAskResponse;
import com.api.sss.model.dto.response.NewsResponse;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Service
public class ModelService {

private final RestTemplate restTemplate = new RestTemplate();
private final String FASTAPI_URL = "http://203.153.147.12:5050";

public List<NewsResponse.Result> cardNews(NewsRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<NewsRequest> entity = new HttpEntity<>(request, headers);

try {
ResponseEntity<NewsResponse> response = restTemplate.exchange(
FASTAPI_URL + "/news",
HttpMethod.POST,
entity,
NewsResponse.class
);
return response.getBody().getResults();
} catch (Exception e) {
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
}
}

public ChatAskResponse askQuestion(ChatAskRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<ChatAskRequest> entity = new HttpEntity<>(request, headers);

try {
ResponseEntity<ChatAskResponse> response = restTemplate.exchange(
FASTAPI_URL + "/chat",
HttpMethod.POST,
entity,
ChatAskResponse.class
);
return response.getBody();
} catch (Exception e) {
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
}
}
private final RestTemplate restTemplate = new RestTemplate();
private final String FASTAPI_URL = "http://203.153.147.12:5050";

public List<NewsResponse.Result> cardNews(NewsRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<NewsRequest> entity = new HttpEntity<>(request, headers);

try {
ResponseEntity<NewsResponse> response = restTemplate.exchange(
FASTAPI_URL + "/news",
HttpMethod.POST,
entity,
NewsResponse.class
);
return response.getBody().getResults();
} catch (Exception e) {
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
}
}

public ChatAskResponse askQuestion(ChatAskRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<ChatAskRequest> entity = new HttpEntity<>(request, headers);

try {
ResponseEntity<ChatAskResponse> response = restTemplate.exchange(
FASTAPI_URL + "/chat",
HttpMethod.POST,
entity,
ChatAskResponse.class
);
return response.getBody();
} catch (Exception e) {
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
}
}

public String predictCoin(String coinName) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

Map<String, String> body = new HashMap<>();
body.put("coinName", coinName);
HttpEntity<Map<String, String>> entity = new HttpEntity<>(body, headers);

try {
// TODO: return response.getBody();
// ResponseEntity<String> response = restTemplate.exchange(
// FASTAPI_URL + "/chat/predict",
// HttpMethod.POST,
// entity,
// String.class
// );
return coinName;
} catch (Exception e) {
throw new CustomException(ErrorCode.FASTAPI_COMMUNICATION_ERROR);
}
}
}
56 changes: 56 additions & 0 deletions src/main/java/com/api/sss/stt/controller/SttController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.api.sss.stt.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.api.sss.config.response.dto.CustomResponse;
import com.api.sss.config.response.dto.SuccessStatus;
import com.api.sss.stt.dto.response.ChatPredictResponse;
import com.api.sss.stt.service.SttService;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1")
public class SttController {

private final SttService sttService;

@Operation(
summary = "코인 시세 예측 API",
description = "STT로 코인종목 명을 입력받아 FastAPI 서비스에 전달하고 답변을 받아옵니다."
)
@ApiResponse(responseCode = "200", description = "답변 수신 성공")
@ApiResponse(
responseCode = "500",
description = "FastAPI 서비스 오류 또는 통신 실패",
content = @Content(mediaType = "application/json", examples = @ExampleObject(value = """
{
"code": 500,
"message": "FastAPI와의 통신 중 오류가 발생했습니다."
}
"""))
// 종목명 인식 실패했을 시 response 추가 필요
)
@PostMapping(path = "/chat/predict", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public CustomResponse<ChatPredictResponse> predictCoin(
@Parameter(
description = "업로드할 44100Hz mp3 음성 파일",
content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE)
)
@RequestParam("file") MultipartFile file) {
ChatPredictResponse response = sttService.predictCoin(file);
return CustomResponse.success(response, SuccessStatus.SUCCESS);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.api.sss.stt.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class ChatPredictResponse {
private String prediction;
}
Loading