diff --git a/app/error.vue b/app/error.vue index 627dd21..6ae33a5 100644 --- a/app/error.vue +++ b/app/error.vue @@ -67,7 +67,21 @@ interface ErrorProps { } } -const { tm } = useI18n() +// Configuration +const runtimeConfig = useRuntimeConfig() +const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string +const multilingualFileName = 'STOVE_PUBTEMPLATE_common_error-message.json' + +// Multilingual +const resultGetMultilingual = await useGetMultilingual({ + baseApiUrl: dataResourcesUrl, + fileName: multilingualFileName, +}) +const { tm }: any = useI18n({ + useScope: 'local', + messages: Object(resultGetMultilingual?.value?.multilingual), +}) + const props = withDefaults(defineProps(), { error: () => ({}), }) @@ -107,6 +121,7 @@ const handleKeydown = (e: KeyboardEvent) => { // 500 에러 발생 시 /error 페이지로 리다이렉트 onMounted(() => { const statusCode = currentError.value?.statusCode + console.log("🚀 ~ statusCode:", nuxtError) if (statusCode === 500) { errorTitle.value = tm('Error_500_Inconvenience') diff --git a/error.vue b/error.vue index c1b40a7..40a5c2d 100644 --- a/error.vue +++ b/error.vue @@ -3,49 +3,61 @@ -
+
- -
- Stove -
- - -
- -
- Error +
+ Stove
- -
-

- -

-

-
-
+ +
+ +
+ Error +
- - - - - + +
+

+ +

+

+
+
+ + + + +
- \ No newline at end of file + window.addEventListener('keydown', handleKeydown) + window.addEventListener('popstate', handleBack) +}) +onUnmounted(() => { + window.removeEventListener('keydown', handleKeydown) + window.removeEventListener('popstate', handleBack) +}) + diff --git a/i18n/locales/de.ts b/i18n/locales/de.ts index 6bb0418..f0ed36d 100644 --- a/i18n/locales/de.ts +++ b/i18n/locales/de.ts @@ -3,7 +3,7 @@ import fallback from './fallback/de' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/en.ts b/i18n/locales/en.ts index 711fa5b..883038d 100644 --- a/i18n/locales/en.ts +++ b/i18n/locales/en.ts @@ -3,7 +3,7 @@ import fallback from './fallback/en' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/es.ts b/i18n/locales/es.ts index 0a9ff28..f98a9d2 100644 --- a/i18n/locales/es.ts +++ b/i18n/locales/es.ts @@ -3,7 +3,7 @@ import fallback from './fallback/es' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/fr.ts b/i18n/locales/fr.ts index 4edfa66..84ecf99 100644 --- a/i18n/locales/fr.ts +++ b/i18n/locales/fr.ts @@ -3,7 +3,7 @@ import fallback from './fallback/fr' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/ja.ts b/i18n/locales/ja.ts index 59ab368..783a64c 100644 --- a/i18n/locales/ja.ts +++ b/i18n/locales/ja.ts @@ -3,7 +3,7 @@ import fallback from './fallback/ja' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/ko.ts b/i18n/locales/ko.ts index e95ea13..60b9316 100644 --- a/i18n/locales/ko.ts +++ b/i18n/locales/ko.ts @@ -3,7 +3,7 @@ import fallback from './fallback/ko' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/pt.ts b/i18n/locales/pt.ts index 53d1e2c..5a99ee1 100644 --- a/i18n/locales/pt.ts +++ b/i18n/locales/pt.ts @@ -3,7 +3,7 @@ import fallback from './fallback/pt' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/th.ts b/i18n/locales/th.ts index 0c3f036..daa657f 100644 --- a/i18n/locales/th.ts +++ b/i18n/locales/th.ts @@ -3,7 +3,7 @@ import fallback from './fallback/th' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/zh-cn.ts b/i18n/locales/zh-cn.ts index f351917..bf960f7 100644 --- a/i18n/locales/zh-cn.ts +++ b/i18n/locales/zh-cn.ts @@ -3,7 +3,7 @@ import fallback from './fallback/zh-cn' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/i18n/locales/zh-tw.ts b/i18n/locales/zh-tw.ts index 22bd4c3..637339c 100644 --- a/i18n/locales/zh-tw.ts +++ b/i18n/locales/zh-tw.ts @@ -3,7 +3,7 @@ import fallback from './fallback/zh-tw' export default defineI18nLocale(async (locale: string) => { const runtimeConfig = useRuntimeConfig() const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string - const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' + const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json' try { const resultGetMultilingual = await useGetMultilingual({ baseApiUrl: dataResourcesUrl, diff --git a/layers/middleware/init.route.global.ts b/layers/middleware/init.route.global.ts index 17e7151..590fa38 100644 --- a/layers/middleware/init.route.global.ts +++ b/layers/middleware/init.route.global.ts @@ -59,6 +59,16 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { to.path.includes('/error') || to.path.includes('/inspection') ) { + console.log("🚀 ~ init.route.global error 페이지는 API 호출하지 않음") + showError( + createError({ + statusCode: 500, + statusMessage: + 'Internal Server Error', + fatal: false, // 즉시 에러 페이지로 + data: { reason: 'post-not-found' }, + }) + ) return } diff --git a/layers/middleware/inspection.ts b/layers/middleware/inspection.ts index 26492f7..1943d13 100644 --- a/layers/middleware/inspection.ts +++ b/layers/middleware/inspection.ts @@ -11,6 +11,7 @@ export default defineNuxtRouteMiddleware(async to => { // error 페이지는 API 호출하지 않음 if (pageUrl === '/error' || to.path.includes('/error')) { + console.log("🚀 ~ inspection error 페이지는 API 호출하지 않음") return } @@ -37,8 +38,6 @@ export default defineNuxtRouteMiddleware(async to => { baseApiUrl: stoveApiBaseUrl, gameId: stoveGameId, }) - console.log("🚀 ~ stoveApiBaseUrl:", stoveApiBaseUrl) - console.log("🚀 ~ stoveGameId:", stoveGameId) // 게임 점검 ----- // const { checkGameMaintenance } = useGetGameMaintenance() diff --git a/layers/middleware/pageData.global.ts b/layers/middleware/pageData.global.ts index bced751..b0388c8 100644 --- a/layers/middleware/pageData.global.ts +++ b/layers/middleware/pageData.global.ts @@ -42,6 +42,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { // error 페이지는 API 호출하지 않음 if (pageUrl === '/error' || to.path.includes('/error')) { + console.log("🚀 ~pageData.global error 페이지는 API 호출하지 않음") return } // 페이지 이동 시 로딩 상태 시작 diff --git a/layers/server/middleware/gameData.ts b/layers/server/middleware/gameData.ts index eaa8b4a..9b29328 100644 --- a/layers/server/middleware/gameData.ts +++ b/layers/server/middleware/gameData.ts @@ -1,9 +1,17 @@ import { LRUCache } from 'lru-cache' -import { getHeader, getRequestHost, defineEventHandler } from 'h3' +import { + getHeader, + getRequestHost, + defineEventHandler, + createError, + setCookie, + type H3Event, +} from 'h3' import { ssrGetFinalLocale } from '../../utils/localeUtil' import type { GameDataResponse } from '../../types/api/gameData' -import type { ResGetInspectionData } from '../../types/InspectionType' +import type { ResGetInspectionData, WebInspectionData } from '../../types/InspectionType' import { isStaticFile } from '#layers/utils/commonUtil' +import { getTrueClientIp } from '#layers/utils/apiUtil' /** * 캐시 제어 헤더를 설정하는 공통 함수 @@ -68,7 +76,7 @@ const cache = new LRUCache({ * @param baseDomain - 기본 도메인 */ function setFinalLocaleCookie( - event: any, + event: H3Event, finalLocale: string, baseDomain: string ) { @@ -85,7 +93,7 @@ function setFinalLocaleCookie( * @param event - 이벤트 객체 * @param finalLocale - 최종 언어 */ -function fnLocaleMiddleware(event: any, finalLocale: string) { +function fnLocaleMiddleware(event: H3Event, finalLocale: string) { // 이미 응답이 종료되었는지 확인 if (event.node.res.headersSent || event.node.res.writableEnded) { return @@ -133,43 +141,24 @@ export default defineEventHandler(async event => { return } + if (event.path.includes('/error')) { + return + } + // 이미 응답이 종료되었는지 확인 (리다이렉트 등으로 인한 중복 실행 방지) if (event.node.res.headersSent || event.node.res.writableEnded) { return } - // const runType = `${config.public.runType}` - // console.log("🚀 ~ baseDomain:", config.public.baseDomain) - // const url = getRequestURL(event) - - // if (['local', 'local-gate8', 'dev'].includes(runType)) { - // Sandbox 이상 환경에서만 동작 및 확인 가능 (local, dev는 통과 처리) - // try { - // 언어 코드 추출 - // const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers) - // console.log("🚀 ~ finalLocale:", finalLocale) - // setFinalLocaleCookie(event, finalLocale, baseDomain) - - // ------------------------------------------------------------------------------- - // [Locale Middleware] - // ------------------------------------------------------------------------------- - // fnLocaleMiddleware(event, finalLocale) - // } catch (e) { - // console.error('[Exception] /server/middleware/middleware-02-global: ', e) - // } - // } - const runtimeConfig = useRuntimeConfig() - const iBaseApiUrl = `${runtimeConfig.public.stoveApiUrlServer}` - const baseDomain = `${runtimeConfig.public.baseDomain}` - const stoveApiUrlBaseServer = runtimeConfig.public.stoveApiUrlServer - const apiUrl = `${stoveApiUrlBaseServer}/pub-comm/v1.0/template/game` + const iBaseApiUrl = runtimeConfig.public.stoveApiUrlServer + const baseDomain = runtimeConfig.public.baseDomain + const apiUrl = `${iBaseApiUrl}/pub-comm/v1.0/template/game` - let initGameData: GameDataResponse | null = null let initLangCodes: string[] | null = null - let finalLocale - let cleanHost - let initDefaultLocale + let finalLocale: string + let cleanHost: string | undefined + let initDefaultLocale: string | null = null const host = (getHeader(event, 'host') || getRequestHost(event)).toString() || '' @@ -188,14 +177,13 @@ export default defineEventHandler(async event => { const initResponse = (await $fetch(apiUrl, { query: queryParams, })) as GameDataResponse | null - initGameData = initResponse || null - // console.log("🚀 ~ 00000 initGameData:", initGameData) initLangCodes = initResponse?.value?.lang_codes || null initDefaultLocale = initResponse?.value?.default_lang_code || null - console.log('🚀 ~ initLangCodes:===========', initLangCodes) } catch (error) { console.error('init gameData load error:', error) + + return } const fullPath = event.path @@ -247,28 +235,19 @@ export default defineEventHandler(async event => { initDefaultLocale ) + // 쿼리스트링에서 f 파라미터 값 추출 const path = event?.node.req.url || '' - let queryStringF = '' let fValue = '' - let test500 = false if (path.includes('?')) { - queryStringF = path.split('?')[1] - // 쿼리스트링에서 f 파라미터 값 추출 try { - const urlParams = new URLSearchParams(queryStringF) + const queryString = path.split('?')[1] + const urlParams = new URLSearchParams(queryString) fValue = urlParams.get('f') || '' - // 테스트용 500 에러 발생 (예: ?test500=true) - test500 = urlParams.get('test500') === 'true' } catch (e) { console.error('쿼리스트링 파싱 에러:', e) } } - // 테스트용 500 에러 발생 - if (test500) { - throw new Error('테스트용 500 에러 발생') - } - // 미리보기 API 호출 처리 if (fValue === 'preview') { cleanHost = 'samplegame.onstove.com' @@ -278,7 +257,7 @@ export default defineEventHandler(async event => { game_domain: cleanHost || '', lang_code: finalLocale, } - + const response = (await $fetch(apiUrl, { query: queryParams, })) as GameDataResponse | null @@ -288,14 +267,23 @@ export default defineEventHandler(async event => { setFinalLocaleCookie(event, finalLocale, baseDomain) } + if (response?.code === 91001) { + // 91001 에러 발생 시 바로 /error 페이지로 리다이렉트 + if (!event.node.res.headersSent && !event.node.res.writableEnded) { + const errorPath = `/${finalLocale || 'ko'}/error` + event.node.res.statusCode = 302 + event.node.res.setHeader('Location', errorPath) + event.node.res.end() + return + } + } + if (response?.code === 0 && 'value' in response) { event.context.gameData = response.value event.context.googleAnalyticsId = response.value?.ga_code - console.log('🚀 ~ gameData:', response.value) - // 점검 데이터 조회 - let inspectionData + let inspectionData: WebInspectionData | undefined if (cache.has(cacheKey)) { inspectionData = cache.get(cacheKey) as WebInspectionData } else { @@ -333,50 +321,39 @@ export default defineEventHandler(async event => { ) { /** * 점검 중인 경우 - * - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ + * - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인 * - 점검 URL 경로가 아닐 경우 no-cache 설정 * - 화이트 리스트 체크 */ - // 현재 경로가 점검 페이지가 아닐 경우 리다이렉트 const inspectionPath = `/${finalLocale}/inspection` - if (fullPath !== inspectionPath) { + const isInspectionPath = fullPath === inspectionPath + + // 현재 경로가 점검 페이지가 아닐 경우 캐시 헤더 설정 + if (!isInspectionPath) { setCacheHeaders(event, 'no-cache') } + // 응답이 이미 종료되었는지 확인 + if (event.node.res.headersSent || event.node.res.writableEnded) { + return + } + // 점검 중일 때 IP 필터링 활성화 여부 확인 if (inspectionData?.ip_filter_use_yn === 'Y') { const clientIP = getTrueClientIp(event.node.req as any) - // 점검 중일 때 IP 필터링 활성화 여부 확인 - if (inspectionData?.ip_filter_use_yn === 'Y') { - const clientIP = getTrueClientIp(event.node.req as any) - - // 허용된 IP 목록 확인 - if (!inspectionData?.ip_filter_list?.includes(clientIP)) { - // 허용되지 않은 IP인 경우 점검 페이지로 이동 - if ( - !event.node.res.headersSent && - !event.node.res.writableEnded - ) { - event.node.res.statusCode = 302 - event.node.res.setHeader('Location', inspectionPath) - event.node.res.end() - } - } else { - // 화이트 리스트인 경우 - // ------------------------------------------------------------------------------- - // [Locale Middleware] - // ------------------------------------------------------------------------------- - fnLocaleMiddleware(event, finalLocale) - } + // 허용된 IP 목록 확인 + if (inspectionData?.ip_filter_list?.includes(clientIP)) { + // 화이트 리스트인 경우 Locale Middleware 실행 + fnLocaleMiddleware(event, finalLocale) } else { - if (!event.node.res.headersSent && !event.node.res.writableEnded) { - event.node.res.statusCode = 302 - event.node.res.setHeader('Location', inspectionPath) - event.node.res.end() - } + // 허용되지 않은 IP인 경우 점검 페이지로 이동 + event.node.res.statusCode = 302 + event.node.res.setHeader('Location', inspectionPath) + event.node.res.end() } } else { + // IP 필터링이 비활성화된 경우 모든 사용자를 점검 페이지로 이동 event.node.res.statusCode = 302 event.node.res.setHeader('Location', inspectionPath) event.node.res.end() @@ -443,37 +420,40 @@ export default defineEventHandler(async event => { fnLocaleMiddleware(event, finalLocale) } } - } catch (error) { + } catch (error: any) { console.error('gameData load error:', error) - // 500 에러 발생 시 /error 페이지로 리다이렉트 - if (!event.node.res.headersSent && !event.node.res.writableEnded) { - // 언어 코드 추출 시도 - let finalLocale = 'ko' // 기본값 - try { - finalLocale = ssrGetFinalLocale( - event?.node.req.url, - event.node.req.headers, - initLangCodes, - initDefaultLocale - ) - } catch (e) { - console.error('Locale extraction error:', e) - } - // finalLocale이 undefined인 경우 기본값으로 'ko' 설정 - - console.log('🚀 ~ 여기도 타? error:', error) - throw createError({ - statusCode: error.statusCode, - statusMessage: error.statusMessage, - }) - // if (!finalLocale) { - // finalLocale = 'ko' - // } - // const errorPath = `/${finalLocale}/error?message=${error.message}` - // event.node.res.statusCode = 302 - // event.node.res.setHeader('Location', errorPath) - // event.node.res.end() + // 응답이 이미 종료되었는지 확인 + if (event.node.res.headersSent || event.node.res.writableEnded) { + return } + + // 언어 코드 추출 시도 + let errorLocale = 'ko' // 기본값 + try { + errorLocale = ssrGetFinalLocale( + event?.node.req.url, + event.node.req.headers, + initLangCodes, + initDefaultLocale + ) + } catch (e) { + console.error('Locale extraction error:', e) + } + + // 91001 에러인 경우 바로 리다이렉트 + if (error?.statusCode === 91001 || error?.cause?.statusCode === 91001) { + const errorPath = `/${errorLocale}/error` + event.node.res.statusCode = 302 + event.node.res.setHeader('Location', errorPath) + event.node.res.end() + return + } + + // 다른 에러는 기존대로 throw + throw createError({ + statusCode: error?.statusCode || 500, + statusMessage: error?.statusMessage, + }) } }) diff --git a/tsconfig.json b/tsconfig.json index 698edbb..372b536 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,7 @@ ".nuxt/nuxt.d.ts", ".nuxt/auto-imports.d.ts", "types/**/*", + "i18n/**/*", "layers/**/*", "app/**/*" , "temp/inspection.ts", "temp/middleware.ts" ], diff --git a/types/i18n.d.ts b/types/i18n.d.ts new file mode 100644 index 0000000..2a03ac4 --- /dev/null +++ b/types/i18n.d.ts @@ -0,0 +1,8 @@ +declare global { + function defineI18nLocale( + loader: (locale: string) => Promise> | Record + ): any +} + +export {} +