2025-06-18 15:02:07 +09:00

225 lines
7.0 KiB
JavaScript

//* src/services/api.js - 수정된 API URL 설정
import axios from 'axios'
// 런타임 환경 설정에서 API URL 가져오기
const getApiUrls = () => {
const config = window.__runtime_config__ || {}
return {
GATEWAY_URL: config.GATEWAY_URL || 'http://20.1.2.3',
AUTH_URL: config.AUTH_URL || 'http://smarketing.20.249.184.228.nip.io/api/auth',
MEMBER_URL: config.MEMBER_URL || 'http://smarketing.20.249.184.228.nip.io/api/member',
STORE_URL: config.STORE_URL || 'http://smarketing.20.249.184.228.nip.io/api/store',
CONTENT_URL: config.CONTENT_URL || 'http://smarketing.20.249.184.228.nip.io/api/content',
MENU_URL: config.MENU_URL || 'http://smarketing.20.249.184.228.nip.io/api/menu',
// ⚠️ 수정: 매출 API는 store 서비스 (포트 8082)
SALES_URL: config.SALES_URL || 'http://smarketing.20.249.184.228.nip.io/api/sales',
// ⚠️ 수정: 추천 API는 ai-recommend 서비스 (포트 8084)
RECOMMEND_URL: config.RECOMMEND_URL || 'http://smarketing.20.249.184.228.nip.io/api/recommendations'
}
}
// Axios 인스턴스 생성
const createApiInstance = (baseURL) => {
const instance = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
})
// 요청 인터셉터 - JWT 토큰 자동 추가
instance.interceptors.request.use(
(config) => {
const token = localStorage.getItem('accessToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
// ⚠️ 추가: 요청 로깅 (개발 환경에서만)
if (import.meta.env.DEV) {
console.log(`🌐 [API_REQ] ${config.method?.toUpperCase()} ${config.baseURL}${config.url}`)
}
return config
},
(error) => {
return Promise.reject(error)
},
)
// 응답 인터셉터 - 토큰 갱신 및 에러 처리
instance.interceptors.response.use(
(response) => {
// ⚠️ 추가: 응답 로깅 (개발 환경에서만)
if (import.meta.env.DEV) {
console.log(`✅ [API_RES] ${response.status} ${response.config?.method?.toUpperCase()} ${response.config?.url}`)
}
return response
},
async (error) => {
// ⚠️ 추가: 에러 로깅 (개발 환경에서만)
if (import.meta.env.DEV) {
console.error(`❌ [API_ERR] ${error.response?.status || 'Network'} ${error.config?.method?.toUpperCase()} ${error.config?.url}`, error.response?.data)
}
const originalRequest = error.config
// 401 에러이고 토큰 갱신을 시도하지 않은 경우
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
const refreshToken = localStorage.getItem('refreshToken')
if (refreshToken) {
const refreshResponse = await axios.post(`${getApiUrls().AUTH_URL}/refresh`, {
refreshToken,
})
const { accessToken, refreshToken: newRefreshToken } = refreshResponse.data.data
localStorage.setItem('accessToken', accessToken)
localStorage.setItem('refreshToken', newRefreshToken)
// 원래 요청에 새 토큰으로 재시도
originalRequest.headers.Authorization = `Bearer ${accessToken}`
return instance(originalRequest)
}
} catch (refreshError) {
// 토큰 갱신 실패 시 로그아웃 처리
console.warn('⚠️ [TOKEN] 토큰 갱신 실패, 로그아웃 처리')
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
localStorage.removeItem('userInfo')
window.location.href = '/login'
}
}
return Promise.reject(error)
},
)
return instance
}
// API 인스턴스들 생성
const apiUrls = getApiUrls()
// ⚠️ 추가: API URL 확인 로깅 (개발 환경에서만)
if (import.meta.env.DEV) {
console.log('🔧 [API_CONFIG] API URLs 설정:', apiUrls)
}
export const memberApi = createApiInstance(apiUrls.MEMBER_URL)
export const authApi = createApiInstance(apiUrls.AUTH_URL)
export const storeApi = createApiInstance(apiUrls.STORE_URL)
export const contentApi = createApiInstance(apiUrls.CONTENT_URL)
export const menuApi = createApiInstance(apiUrls.MENU_URL)
export const salesApi = createApiInstance(apiUrls.SALES_URL)
export const recommendApi = createApiInstance(apiUrls.RECOMMEND_URL)
// 기본 API 인스턴스 (Gateway URL 사용)
export const api = createApiInstance(apiUrls.GATEWAY_URL)
// 공통 에러 핸들러
export const handleApiError = (error) => {
const response = error.response
if (!response) {
return {
success: false,
message: '네트워크 연결을 확인해주세요.',
error: error.message
}
}
const status = response.status
const data = response.data
switch (status) {
case 400:
return {
success: false,
message: data?.message || '잘못된 요청입니다.',
error: data?.error
}
case 401:
return {
success: false,
message: '인증이 필요합니다.',
error: 'UNAUTHORIZED'
}
case 403:
return {
success: false,
message: '접근 권한이 없습니다.',
error: 'FORBIDDEN'
}
case 404:
return {
success: false,
message: '요청한 리소스를 찾을 수 없습니다.',
error: 'NOT_FOUND'
}
case 500:
return {
success: false,
message: '서버 오류가 발생했습니다.',
error: 'INTERNAL_SERVER_ERROR'
}
default:
return {
success: false,
message: data?.message || `오류가 발생했습니다. (${status})`,
error: data?.error
}
}
}
// 성공 응답 포맷터
export const formatSuccessResponse = (data, message = '요청이 성공적으로 처리되었습니다.') => {
return {
success: true,
message,
data
}
}
// ⚠️ 추가: API 상태 확인 함수
export const checkApiHealth = async () => {
const results = {}
try {
// 각 API 서버 상태 확인
const checks = [
{ name: 'Auth', api: authApi, endpoint: '/health' },
{ name: 'Store', api: storeApi, endpoint: '/health' },
{ name: 'Sales', api: salesApi, endpoint: '/health' },
{ name: 'Recommend', api: recommendApi, endpoint: '/health' }
]
for (const check of checks) {
try {
await check.api.get(check.endpoint)
results[check.name] = 'OK'
} catch (error) {
results[check.name] = `ERROR: ${error.response?.status || 'Network'}`
}
}
} catch (error) {
console.error('API 상태 확인 실패:', error)
}
return results
}
// ⚠️ 추가: 개발 환경에서 전역 노출
if (import.meta.env.DEV) {
window.__api_debug__ = {
urls: apiUrls,
instances: { memberApi, authApi, storeApi, contentApi, menuApi, salesApi, recommendApi },
checkHealth: checkApiHealth
}
console.log('🔧 [DEBUG] API 인스턴스가 window.__api_debug__에 노출됨')
}