Skip to content

Commit 5c5f311

Browse files
committed
feat: 웹소캣 데이터 전송 구조 변경
type : data 방식으로 수정 웹소캣 데이터 형태및 이벤트에 따라 데이터가 달라질 수 있고 좀더 유연한 type 검사를 목적으로 함함
1 parent 1991271 commit 5c5f311

File tree

8 files changed

+211
-43
lines changed

8 files changed

+211
-43
lines changed

ProjectVG.Api/Controllers/HomeController.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,18 @@ public IActionResult GetConfig()
1717
version = "1.0.0"
1818
});
1919
}
20+
21+
[HttpGet("health")]
22+
[HttpGet("/health")]
23+
public IActionResult Health()
24+
{
25+
return Ok(new
26+
{
27+
status = "healthy",
28+
timestamp = DateTime.UtcNow,
29+
version = "1.0.0",
30+
uptime = Environment.TickCount / 1000.0
31+
});
32+
}
2033
}
2134
}

ProjectVG.Application/Middlewares/WebSocketMiddleware.cs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.AspNetCore.Http;
55
using Microsoft.Extensions.DependencyInjection;
66
using ProjectVG.Application.Services.Session;
7+
using ProjectVG.Application.Models.Chat;
78

89
namespace ProjectVG.Application.Middlewares
910
{
@@ -53,7 +54,7 @@ public async Task InvokeAsync(HttpContext context)
5354
await sessionService.RegisterSessionAsync(sessionId, socket);
5455

5556
// 클라이언트에게 세션 ID 전송
56-
await SendSessionIdToClient(socket, sessionId);
57+
await sessionService.SendSessionIdAsync(sessionId);
5758

5859
// WebSocket 연결 유지
5960
await KeepWebSocketAlive(socket, sessionId, sessionService);
@@ -69,25 +70,6 @@ private string GenerateSessionId()
6970
return $"session_{DateTime.UtcNow.Ticks}_{Guid.NewGuid().ToString("N")[..8]}";
7071
}
7172

72-
private async Task SendSessionIdToClient(WebSocket socket, string sessionId)
73-
{
74-
try
75-
{
76-
var message = $"{{\"type\":\"session_id\",\"session_id\":\"{sessionId}\"}}";
77-
var buffer = System.Text.Encoding.UTF8.GetBytes(message);
78-
await socket.SendAsync(
79-
new ArraySegment<byte>(buffer),
80-
WebSocketMessageType.Text,
81-
true,
82-
CancellationToken.None
83-
);
84-
}
85-
catch (Exception ex)
86-
{
87-
_logger.LogError(ex, "세션 ID 전송 실패: {SessionId}", sessionId);
88-
}
89-
}
90-
9173
private async Task KeepWebSocketAlive(WebSocket socket, string sessionId, ISessionService sessionService)
9274
{
9375
var buffer = new byte[1024];
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace ProjectVG.Application.Models.Chat
4+
{
5+
/// <summary>
6+
/// WebSocket 메시지 기본 구조
7+
/// </summary>
8+
public class WebSocketMessage
9+
{
10+
[JsonPropertyName("type")]
11+
public string Type { get; set; } = string.Empty;
12+
13+
[JsonPropertyName("data")]
14+
public object Data { get; set; } = new();
15+
16+
public WebSocketMessage() { }
17+
18+
public WebSocketMessage(string type, object data)
19+
{
20+
Type = type;
21+
Data = data;
22+
}
23+
}
24+
}

ProjectVG.Application/Services/Chat/Handlers/ResultSender.cs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,21 @@ public async Task SendResultAsync(ChatPreprocessContext context, ChatProcessResu
2929
continue;
3030
}
3131

32-
// 통합 메시지로 전송
33-
await _sessionService.SendIntegratedMessageAsync(
34-
context.SessionId,
35-
segment.Text,
36-
segment.AudioData,
37-
segment.AudioContentType ?? "wav",
38-
segment.AudioLength
39-
);
32+
// WebSocket 메시지로 래핑하여 전송
33+
var integratedMessage = new IntegratedChatMessage
34+
{
35+
SessionId = context.SessionId,
36+
Text = segment.Text,
37+
AudioFormat = segment.AudioContentType ?? "wav",
38+
AudioLength = segment.AudioLength,
39+
Timestamp = DateTime.UtcNow
40+
};
41+
42+
// 오디오 데이터를 Base64로 변환
43+
integratedMessage.SetAudioData(segment.AudioData);
44+
45+
var wsMessage = new WebSocketMessage("chat", integratedMessage);
46+
await _sessionService.SendWebSocketMessageAsync(context.SessionId, wsMessage);
4047

4148
_logger.LogDebug("세그먼트 전송 완료: 세션 {SessionId}, 순서 {Order}, 텍스트: {HasText}, 오디오: {HasAudio}",
4249
context.SessionId, segment.Order, segment.HasText, segment.HasAudio);

ProjectVG.Application/Services/Session/ISessionService.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using ProjectVG.Infrastructure.Services.Session;
22
using System.Net.WebSockets;
3+
using ProjectVG.Application.Models.Chat;
34

45
namespace ProjectVG.Application.Services.Session
56
{
@@ -70,5 +71,18 @@ public interface ISessionService
7071
/// <param name="sessionId">세션 ID</param>
7172
/// <returns>세션 존재 여부</returns>
7273
Task<bool> SessionExistsAsync(string sessionId);
74+
75+
/// <summary>
76+
/// WebSocket 메시지를 전송합니다
77+
/// </summary>
78+
/// <param name="sessionId">세션 ID</param>
79+
/// <param name="message">전송할 WebSocket 메시지</param>
80+
Task SendWebSocketMessageAsync(string sessionId, WebSocketMessage message);
81+
82+
/// <summary>
83+
/// 세션 ID를 클라이언트에게 전송합니다
84+
/// </summary>
85+
/// <param name="sessionId">세션 ID</param>
86+
Task SendSessionIdAsync(string sessionId);
7387
}
7488
}

ProjectVG.Application/Services/Session/SessionService.cs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ public async Task SendIntegratedMessageAsync(string sessionId, string? text = nu
139139
// 오디오 데이터를 Base64로 변환
140140
integratedMessage.SetAudioData(audioData);
141141

142-
var jsonMessage = JsonSerializer.Serialize(integratedMessage);
142+
// WebSocket 메시지로 래핑
143+
var wsMessage = new WebSocketMessage("chat", integratedMessage);
144+
var jsonMessage = JsonSerializer.Serialize(wsMessage);
143145
var buffer = Encoding.UTF8.GetBytes(jsonMessage);
144146

145147
await connection.WebSocket.SendAsync(
@@ -276,5 +278,53 @@ public async Task<bool> SessionExistsAsync(string sessionId)
276278
return false;
277279
}
278280
}
281+
282+
public async Task SendWebSocketMessageAsync(string sessionId, WebSocketMessage message)
283+
{
284+
try
285+
{
286+
var connection = await _sessionRepository.GetAsync(sessionId);
287+
if (connection == null)
288+
{
289+
_logger.LogWarning("세션을 찾을 수 없음: {SessionId}", sessionId);
290+
return;
291+
}
292+
293+
var jsonMessage = JsonSerializer.Serialize(message);
294+
var buffer = Encoding.UTF8.GetBytes(jsonMessage);
295+
296+
await connection.WebSocket.SendAsync(
297+
new ArraySegment<byte>(buffer),
298+
WebSocketMessageType.Text,
299+
true,
300+
CancellationToken.None
301+
);
302+
303+
_logger.LogInformation("WebSocket 메시지 전송 완료: {SessionId}, 타입: {MessageType}",
304+
sessionId, message.Type);
305+
}
306+
catch (Exception ex)
307+
{
308+
_logger.LogError(ex, "WebSocket 메시지 전송 실패: {SessionId}", sessionId);
309+
throw;
310+
}
311+
}
312+
313+
public async Task SendSessionIdAsync(string sessionId)
314+
{
315+
try
316+
{
317+
var sessionData = new { session_id = sessionId };
318+
var wsMessage = new WebSocketMessage("session_id", sessionData);
319+
await SendWebSocketMessageAsync(sessionId, wsMessage);
320+
321+
_logger.LogInformation("세션 ID 전송 완료: {SessionId}", sessionId);
322+
}
323+
catch (Exception ex)
324+
{
325+
_logger.LogError(ex, "세션 ID 전송 실패: {SessionId}", sessionId);
326+
throw;
327+
}
328+
}
279329
}
280330
}

run-prod.ps1

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
11
# 프로덕션 환경 실행 스크립트
22
Write-Host "🚀 ProjectVG 프로덕션 환경 시작 중..." -ForegroundColor Green
33

4-
# 기존 컨테이너 정리
5-
Write-Host "📦 기존 컨테이너 정리 중..." -ForegroundColor Yellow
4+
# 1. 새 이미지 빌드 (기존 컨테이너는 그대로 유지)
5+
Write-Host "🔨 새 이미지 빌드 중..." -ForegroundColor Yellow
6+
docker-compose build --no-cache
7+
8+
# 2. 빌드 성공 확인
9+
if ($LASTEXITCODE -ne 0) {
10+
Write-Host "❌ 이미지 빌드 실패!" -ForegroundColor Red
11+
exit 1
12+
}
13+
14+
# 3. 기존 컨테이너 중지 및 제거
15+
Write-Host "📦 기존 컨테이너 교체 중..." -ForegroundColor Yellow
616
docker-compose down
717

8-
# 프로덕션 환경 빌드 및 실행
9-
Write-Host "🔨 프로덕션 환경 빌드 및 실행 중..." -ForegroundColor Yellow
10-
docker-compose up --build -d
18+
# 4. 새 컨테이너 시작
19+
Write-Host "🚀 새 컨테이너 시작 중..." -ForegroundColor Yellow
20+
docker-compose up -d
21+
22+
# 5. 시작 확인
23+
Write-Host "⏳ 컨테이너 시작 대기 중..." -ForegroundColor Yellow
24+
Start-Sleep -Seconds 5
1125

12-
Write-Host "✅ 프로덕션 환경이 시작되었습니다!" -ForegroundColor Green
13-
Write-Host "🌐 API: http://localhost:7900" -ForegroundColor Cyan
14-
Write-Host "📚 Swagger: http://localhost:7900/swagger" -ForegroundColor Cyan
26+
# 6. 상태 확인
27+
$containers = docker-compose ps -q
28+
if ($containers) {
29+
Write-Host "✅ 프로덕션 환경이 성공적으로 시작되었습니다!" -ForegroundColor Green
30+
Write-Host "🌐 API: http://localhost:7900" -ForegroundColor Cyan
31+
Write-Host "📚 Swagger: http://localhost:7900/swagger" -ForegroundColor Cyan
32+
Write-Host "📊 컨테이너 상태:" -ForegroundColor Cyan
33+
docker-compose ps
34+
} else {
35+
Write-Host "❌ 컨테이너 시작 실패!" -ForegroundColor Red
36+
exit 1
37+
}

test-clients/test-client.html

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,22 +251,77 @@
251251
if (typeof event.data === "string") {
252252
try {
253253
const data = JSON.parse(event.data);
254+
console.log("수신된 메시지:", data);
254255

255-
// 세션 ID 처리
256+
// 새로운 WebSocket 메시지 구조 처리 (우선순위)
257+
if (data.type && data.data !== undefined) {
258+
console.log(`메시지 타입: ${data.type}`);
259+
260+
// 세션 ID 처리
261+
if (data.type === "session_id") {
262+
sessionId = data.data.session_id;
263+
appendLog(`<b>[세션 ID: ${sessionId}]</b>`);
264+
return;
265+
}
266+
267+
// 채팅 메시지 처리
268+
if (data.type === "chat") {
269+
const chatData = data.data;
270+
let messageText = "";
271+
272+
// 텍스트 메시지가 있는 경우
273+
if (chatData.text) {
274+
messageText += `<b>AI:</b> ${chatData.text}`;
275+
}
276+
277+
// 오디오 데이터가 있는 경우
278+
if (chatData.audioData) {
279+
try {
280+
// Base64 디코딩
281+
const audioBytes = atob(chatData.audioData);
282+
const audioArray = new Uint8Array(audioBytes.length);
283+
for (let i = 0; i < audioBytes.length; i++) {
284+
audioArray[i] = audioBytes.charCodeAt(i);
285+
}
286+
287+
const blob = new Blob([audioArray], { type: `audio/${chatData.audioFormat || 'wav'}` });
288+
audioQueue.push(blob);
289+
if (!isPlayingAudio) playNextAudio();
290+
291+
messageText += chatData.text ? " [오디오 포함]" : "<b>AI:</b> [음성 메시지]";
292+
} catch (e) {
293+
console.error("오디오 데이터 파싱 오류:", e);
294+
messageText += " [오디오 파싱 오류]";
295+
}
296+
}
297+
298+
if (messageText) {
299+
appendLog(messageText);
300+
}
301+
302+
// 메타데이터 표시 (개발용)
303+
if (chatData.metadata) {
304+
appendLog(`<small style='color:#666'>[메타데이터: ${JSON.stringify(chatData.metadata)}]</small>`);
305+
}
306+
307+
return;
308+
}
309+
310+
// 기타 메시지 타입 처리
311+
appendLog(`<small style='color:#666'>[메시지 타입: ${data.type}]</small>`);
312+
return;
313+
}
314+
315+
// 하위 호환성을 위한 기존 구조 처리
256316
if (data.type === "session_id") {
257317
sessionId = data.session_id;
258318
appendLog(`<b>[세션 ID: ${sessionId}]</b>`);
259319
return;
260320
}
261321

262-
// 통합 메시지 처리
263322
if (data.type === "chat") {
264323
let messageText = "";
265324

266-
// 메시지 타입 확인
267-
const messageType = data.messageType || "json";
268-
console.log(`메시지 타입: ${messageType}`);
269-
270325
// 텍스트 메시지가 있는 경우
271326
if (data.text) {
272327
messageText += `<b>AI:</b> ${data.text}`;

0 commit comments

Comments
 (0)