다국어 AI 고객 지원 시스템 구축: 실시간 통신과 데스크톱 알림 구현

배경과 문제점

기존 상용 고객 지원 솔루션들은 해외 진출 및 다국어 지원 요구사항에 부적합한 경우가 많습니다. 특히 실시간 대화 경험, 언어별 동적 처리, 오프라인 알림 기능에서 한계가 있었습니다.

  • 지연 문제: HTTP 폴링 방식은 사용자 경험을 저하시키는 높은 지연 시간을 초래합니다.
  • 복잡한 다국어 관리: 단순 번역이 아닌 전체 시스템 메시지와 날짜 형식의 동적 처리 필요
  • 알림 누락: 브라우저 외부에서 발생하는 메시지에 대한 즉각적인 인지 부족
  • 확장성 제약: 초기 개발 이후의 성능 확장을 고려하지 않은 아키텍처

시스템 아키텍처 설계

WebSocket 기반 통신을 중심으로 구성된 마이크로서비스 아키텍처를 채택했습니다.

  • 통신 계층: WebSocket을 이용한 양방향 실시간 데이터 전송
  • 비즈니스 로직: 자연어 이해(NLU) 엔진과 다국어 처리 모듈 포함
  • 데이터 저장소: 대화 내역과 다국어 리소스 관리
  • 푸시 서비스: 오프라인 상태의 사용자에게 데스크톱 알림 제공

핵심 기술 구현

1. WebSocket 연결 관리 (Node.js)

const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
const connections = new Map();

server.on('connection', (socket, request) => {
  let isActive = true;
  let clientId = null;
  
  // 연결 유지 확인
  socket.on('pong', () => isActive = true);
  
  const pingTimer = setInterval(() => {
    if (!isActive) return socket.terminate();
    isActive = false;
    socket.ping();
  }, 30000);
  
  // 메시지 처리
  socket.on('message', async (rawData) => {
    try {
      const payload = JSON.parse(rawData);
      switch(payload.action) {
        case 'authenticate':
          clientId = payload.id;
          connections.set(clientId, socket);
          break;
          
        case 'forward':
          const targetSocket = connections.get(payload.recipient);
          if(targetSocket && targetSocket.readyState === WebSocket.OPEN) {
            targetSocket.send(JSON.stringify({
              sender: clientId,
              text: payload.text,
              sentAt: Date.now()
            }));
          } else {
            await sendPushNotification(payload.recipient, payload.text);
          }
          break;
      }
    } catch(error) {
      console.error('메시지 파싱 오류:', error);
    }
  });
  
  socket.on('close', () => {
    clearInterval(pingTimer);
    if(clientId) connections.delete(clientId);
  });
});

async function sendPushNotification(recipientId, content) {
  console.log(`[알림] ${recipientId}에게 푸시 발송: ${content.substring(0, 50)}...`);
}

2. 국제화(i18n) 처리 (Vue.js)

// i18n/config.js
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import ko from './langs/ko.json';
import en from './langs/en.json';
import jp from './langs/jp.json';

Vue.use(VueI18n);

export const translator = new VueI18n({
  locale: localStorage.getItem('preferred-language') || 'ko',
  fallbackLocale: 'en',
  messages: { ko, en, jp }
});

export async function changeLanguage(newLang) {
  if(translator.locale === newLang) return;
  
  if(!translator.messages[newLang]) {
    const module = await import(`./langs/${newLang}.json`);
    translator.setLocaleMessage(newLang, module.default);
  }
  
  translator.locale = newLang;
  localStorage.setItem('preferred-language', newLang);
}

3. 데스크톱 알림 통합 (Electron)

// main/process.js
const { app, BrowserWindow, Notification, nativeImage } = require('electron');
const path = require('path');

let workbench;
let badgeCounter = 0;

app.whenReady().then(() => {
  initializeWorkbench();
});

function initializeWorkbench() {
  workbench = new BrowserWindow({/* 설정 */});
  workbench.loadFile('index.html');
  
  const { ipcMain } = require('electron');
  ipcMain.on('trigger-alert', (_, alertData) => displaySystemAlert(alertData));
  ipcMain.on('refresh-counter', (_, count) => refreshBadgeIndicator(count));
}

function displaySystemAlert({ title, message }) {
  if(Notification.isSupported() && Notification.permission === 'granted') {
    new Notification({
      title: title || '새로운 메시지',
      body: message,
      icon: path.join(__dirname, 'assets/alert-icon.png')
    }).show();
  }
}

function refreshBadgeIndicator(count) {
  badgeCounter = count;
  
  if(process.platform === 'darwin') {
    app.dock.setBadge(count ? String(count) : '');
  } else if(process.platform === 'win32' && workbench) {
    const overlay = count ? 
      nativeImage.createFromPath(path.join(__dirname, 'assets/badge.png')) : null;
    workbench.setOverlayIcon(overlay, `${count}개의 미확인 메시지`);
  }
}

성능 최적화 전략

WebSocket 연결 확장성

  • Nginx를 통한 로드 밸런싱으로 단일 서버 한계 극복
  • Redis Pub/Sub을 활용한 메시지 큐잉으로 이벤트 루프 차단 방지
  • Protocol Buffers 적용으로 네트워크 트래픽 감소

다국어 리소스 관리

  • LRU 캐시 알고리즘을 이용한 자주 사용되는 언어 패키지 메모리 유지
  • MongoDB와 같은 NoSQL 저장소를 활용한 드물게 사용되는 언어 리소스 외부화

중요한 주의사항

메시지 중복 처리

// 클라이언트에서 UUID 생성
const generateMessageId = () => crypto.randomUUID();

// 서버에서 중복 검사 (Redis 활용 예시)
async function validateUniqueMessage(id) {
  const exists = await redis.exists(`msg:${id}`);
  if(exists) return false;
  
  await redis.setex(`msg:${id}`, 5, 'processed');
  return true;
}

타임존 처리

  • 서버에서는 항상 UTC 시간 기준으로 저장
  • 클라이언트에서 사용자 지역 정보에 따라 표시 형식 변환
  • 상대적 시간 표현 ("오늘 14:30", "어제 09:15") 사용 권장

추가 기능 가능성

  • E2E 암호화: 민감한 정보 보호를 위한 메시지 암호화
  • 자동 번역 연동: Google Translate 또는 DeepL API 통합
  • 스킬 기반 라우팅: 상담원 전문 분야에 따른 대화 할당
  • 읽음 확인 및 입력 중 표시: 실시간 상태 공유 기능 추가

태그: websocket nodejs vuejs Electron I18n

6월 15일 02:43에 게시됨