refactor: gamedata 없을 때 error 페이지 노출 수정, 다국어 파일 경로 수정

This commit is contained in:
“hyeonggkim”
2025-11-19 20:56:41 +09:00
parent 1726ddb15d
commit f29b84e8e1
18 changed files with 230 additions and 167 deletions

View File

@@ -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<ErrorProps>(), { const props = withDefaults(defineProps<ErrorProps>(), {
error: () => ({}), error: () => ({}),
}) })
@@ -107,6 +121,7 @@ const handleKeydown = (e: KeyboardEvent) => {
// 500 에러 발생 시 /error 페이지로 리다이렉트 // 500 에러 발생 시 /error 페이지로 리다이렉트
onMounted(() => { onMounted(() => {
const statusCode = currentError.value?.statusCode const statusCode = currentError.value?.statusCode
console.log("🚀 ~ statusCode:", nuxtError)
if (statusCode === 500) { if (statusCode === 500) {
errorTitle.value = tm('Error_500_Inconvenience') errorTitle.value = tm('Error_500_Inconvenience')

135
error.vue
View File

@@ -3,49 +3,61 @@
<LayoutsStoveHeader /> <LayoutsStoveHeader />
<Transition name="fade"> <Transition name="fade">
<div v-if="!isLoading" class="flex-1 flex items-center justify-center p-25"> <div
v-if="!isLoading"
class="flex-1 flex items-center justify-center p-25"
>
<div class="flex flex-col items-center gap-6 w-full"> <div class="flex flex-col items-center gap-6 w-full">
<!-- Stove Logo --> <!-- Stove Logo -->
<div class="flex items-center justify-center h-7"> <div class="flex items-center justify-center h-7">
<img <img
src="/images/common/logo-stove.svg" src="/images/common/logo-stove.svg"
alt="Stove" alt="Stove"
class="h-full w-auto" class="h-full w-auto"
/>
</div>
<!-- Error Icon and Text -->
<div class="flex flex-col items-center gap-4 w-full">
<!-- Error Icon -->
<div class="flex items-center justify-center">
<img
src="/images/common/img_error.png"
alt="Error"
class="w-40 h-40 md:w-60 md:h-60"
/> />
</div> </div>
<!-- Error Text --> <!-- Error Icon and Text -->
<div class="flex flex-col items-center gap-2 w-full"> <div class="flex flex-col items-center gap-4 w-full">
<h1 class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0"> <!-- Error Icon -->
<span v-dompurify-html="errorTitle"></span> <div class="flex items-center justify-center">
</h1> <img
<p v-dompurify-html="errorMsg" class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"></p> src="/images/common/img_error.png"
</div> alt="Error"
</div> class="w-40 h-40 md:w-60 md:h-60"
/>
</div>
<!-- Home Button --> <!-- Error Text -->
<AtomsButton class="flex items-center justify-center font-medium text-sm leading-[1.4] tracking-[-0.03em] text-white bg-[#FC4420] border border-black/10 rounded-lg px-10 h-12 cursor-pointer transition-all duration-300 backdrop-blur-[30px] shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09)] hover:bg-[#E03D1C] hover:shadow-[0px_0.9131946563720703px_1.4666459560394287px_0px_rgba(0,0,0,0.06),0px_2.194533586502075px_3.5245540142059326px_0px_rgba(0,0,0,0.09),0px_4.1321120262146px_6.636422634124756px_0px_rgba(0,0,0,0.12)] active:translate-y-px" @click="handleError"> <div class="flex flex-col items-center gap-2 w-full">
<span v-dompurify-html="tm('Error_Official_Page')"></span> <h1
</AtomsButton> class="font-medium text-xl md:text-2xl leading-[1.5] tracking-[-0.03em] text-center text-[#1F1F1F] m-0"
>
<span v-dompurify-html="errorTitle"></span>
</h1>
<p
v-dompurify-html="errorMsg"
class="font-normal text-sm md:text-base leading-[1.7] md:leading-[1.625] tracking-[-0.03em] text-center text-[#666666] m-0"
></p>
</div>
</div>
<!-- Home Button -->
<AtomsButton
type="action"
button-size="size-small md:size-large"
background-color="#FC4420"
text-color="#FFFFFF"
@click="handleError"
>
<span v-dompurify-html="tm('Error_Official_Page')"></span>
</AtomsButton>
</div> </div>
</div> </div>
</Transition> </Transition>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
interface ErrorProps { interface ErrorProps {
error?: { error?: {
@@ -55,9 +67,23 @@ 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<ErrorProps>(), { const props = withDefaults(defineProps<ErrorProps>(), {
error: () => ({}) error: () => ({}),
}) })
const nuxtError = useError() const nuxtError = useError()
const currentError = computed(() => props.error || nuxtError.value) const currentError = computed(() => props.error || nuxtError.value)
@@ -67,17 +93,34 @@ const isLoading = ref(true)
const errorTitle = ref('') const errorTitle = ref('')
const errorMsg = ref('') const errorMsg = ref('')
//error clear 함수 생성 //error clear 함수 생성
const localePath = useLocalePath()
const handleError = () => clearError({ redirect: '/' })
// const handleError = () => clearError({ redirect: '/' })
const handleError = () => {
window.location.href = localePath('/')
}
const handleBack = () => {
// 에러 상태를 클리어하고 이전 페이지로 이동
// popstate 이벤트가 이미 발생한 경우이므로 추가 동작 불필요
// 키보드 백스페이스의 경우에만 명시적으로 뒤로가기 실행
window.location.href = localePath('/')
// navigateTo(`${router.currentRoute.value.path}`)
}
// 백스페이스 키 처리
const handleKeydown = (e: KeyboardEvent) => {
if (
e.key === 'Backspace' &&
!['INPUT', 'TEXTAREA'].includes((e.target as HTMLElement).tagName)
) {
e.preventDefault()
handleBack()
}
}
// 500 에러 발생 시 /error 페이지로 리다이렉트 // 500 에러 발생 시 /error 페이지로 리다이렉트
onMounted(() => { onMounted(() => {
const statusCode = currentError.value?.statusCode const statusCode = currentError.value?.statusCode
console.log("🚀 ~ 1111currentError==:", currentError.value?.message)
console.log("🚀 ~ 1111statusCode:", statusCode)
if (statusCode === 500) { if (statusCode === 500) {
errorTitle.value = tm('Error_500_Inconvenience') errorTitle.value = tm('Error_500_Inconvenience')
@@ -86,10 +129,16 @@ onMounted(() => {
errorTitle.value = tm('Error_404_Not_Found') errorTitle.value = tm('Error_404_Not_Found')
errorMsg.value = tm('Error_404_Not_Found2') errorMsg.value = tm('Error_404_Not_Found2')
} }
nextTick(() => { nextTick(() => {
isLoading.value = false isLoading.value = false
}) })
})
</script> window.addEventListener('keydown', handleKeydown)
window.addEventListener('popstate', handleBack)
})
onUnmounted(() => {
window.removeEventListener('keydown', handleKeydown)
window.removeEventListener('popstate', handleBack)
})
</script>

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/de'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/en'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/es'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/fr'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/ja'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/ko'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/pt'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/th'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/zh-cn'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -3,7 +3,7 @@ import fallback from './fallback/zh-tw'
export default defineI18nLocale(async (locale: string) => { export default defineI18nLocale(async (locale: string) => {
const runtimeConfig = useRuntimeConfig() const runtimeConfig = useRuntimeConfig()
const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string
const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' const multilingualFileName = 'STOVE_PUBTEMPLATE_common_translations.json'
try { try {
const resultGetMultilingual = await useGetMultilingual({ const resultGetMultilingual = await useGetMultilingual({
baseApiUrl: dataResourcesUrl, baseApiUrl: dataResourcesUrl,

View File

@@ -59,6 +59,16 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
to.path.includes('/error') || to.path.includes('/error') ||
to.path.includes('/inspection') 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 return
} }

View File

@@ -11,6 +11,7 @@ export default defineNuxtRouteMiddleware(async to => {
// error 페이지는 API 호출하지 않음 // error 페이지는 API 호출하지 않음
if (pageUrl === '/error' || to.path.includes('/error')) { if (pageUrl === '/error' || to.path.includes('/error')) {
console.log("🚀 ~ inspection error 페이지는 API 호출하지 않음")
return return
} }
@@ -37,8 +38,6 @@ export default defineNuxtRouteMiddleware(async to => {
baseApiUrl: stoveApiBaseUrl, baseApiUrl: stoveApiBaseUrl,
gameId: stoveGameId, gameId: stoveGameId,
}) })
console.log("🚀 ~ stoveApiBaseUrl:", stoveApiBaseUrl)
console.log("🚀 ~ stoveGameId:", stoveGameId)
// 게임 점검 ----- // 게임 점검 -----
// const { checkGameMaintenance } = useGetGameMaintenance() // const { checkGameMaintenance } = useGetGameMaintenance()

View File

@@ -42,6 +42,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
// error 페이지는 API 호출하지 않음 // error 페이지는 API 호출하지 않음
if (pageUrl === '/error' || to.path.includes('/error')) { if (pageUrl === '/error' || to.path.includes('/error')) {
console.log("🚀 ~pageData.global error 페이지는 API 호출하지 않음")
return return
} }
// 페이지 이동 시 로딩 상태 시작 // 페이지 이동 시 로딩 상태 시작

View File

@@ -1,9 +1,17 @@
import { LRUCache } from 'lru-cache' 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 { ssrGetFinalLocale } from '../../utils/localeUtil'
import type { GameDataResponse } from '../../types/api/gameData' 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 { isStaticFile } from '#layers/utils/commonUtil'
import { getTrueClientIp } from '#layers/utils/apiUtil'
/** /**
* 캐시 제어 헤더를 설정하는 공통 함수 * 캐시 제어 헤더를 설정하는 공통 함수
@@ -68,7 +76,7 @@ const cache = new LRUCache({
* @param baseDomain - 기본 도메인 * @param baseDomain - 기본 도메인
*/ */
function setFinalLocaleCookie( function setFinalLocaleCookie(
event: any, event: H3Event,
finalLocale: string, finalLocale: string,
baseDomain: string baseDomain: string
) { ) {
@@ -85,7 +93,7 @@ function setFinalLocaleCookie(
* @param event - 이벤트 객체 * @param event - 이벤트 객체
* @param finalLocale - 최종 언어 * @param finalLocale - 최종 언어
*/ */
function fnLocaleMiddleware(event: any, finalLocale: string) { function fnLocaleMiddleware(event: H3Event, finalLocale: string) {
// 이미 응답이 종료되었는지 확인 // 이미 응답이 종료되었는지 확인
if (event.node.res.headersSent || event.node.res.writableEnded) { if (event.node.res.headersSent || event.node.res.writableEnded) {
return return
@@ -133,43 +141,24 @@ export default defineEventHandler(async event => {
return return
} }
if (event.path.includes('/error')) {
return
}
// 이미 응답이 종료되었는지 확인 (리다이렉트 등으로 인한 중복 실행 방지) // 이미 응답이 종료되었는지 확인 (리다이렉트 등으로 인한 중복 실행 방지)
if (event.node.res.headersSent || event.node.res.writableEnded) { if (event.node.res.headersSent || event.node.res.writableEnded) {
return 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 runtimeConfig = useRuntimeConfig()
const iBaseApiUrl = `${runtimeConfig.public.stoveApiUrlServer}` const iBaseApiUrl = runtimeConfig.public.stoveApiUrlServer
const baseDomain = `${runtimeConfig.public.baseDomain}` const baseDomain = runtimeConfig.public.baseDomain
const stoveApiUrlBaseServer = runtimeConfig.public.stoveApiUrlServer const apiUrl = `${iBaseApiUrl}/pub-comm/v1.0/template/game`
const apiUrl = `${stoveApiUrlBaseServer}/pub-comm/v1.0/template/game`
let initGameData: GameDataResponse | null = null
let initLangCodes: string[] | null = null let initLangCodes: string[] | null = null
let finalLocale let finalLocale: string
let cleanHost let cleanHost: string | undefined
let initDefaultLocale let initDefaultLocale: string | null = null
const host = const host =
(getHeader(event, 'host') || getRequestHost(event)).toString() || '' (getHeader(event, 'host') || getRequestHost(event)).toString() || ''
@@ -188,14 +177,13 @@ export default defineEventHandler(async event => {
const initResponse = (await $fetch(apiUrl, { const initResponse = (await $fetch(apiUrl, {
query: queryParams, query: queryParams,
})) as GameDataResponse | null })) as GameDataResponse | null
initGameData = initResponse || null
// console.log("🚀 ~ 00000 initGameData:", initGameData)
initLangCodes = initResponse?.value?.lang_codes || null initLangCodes = initResponse?.value?.lang_codes || null
initDefaultLocale = initResponse?.value?.default_lang_code || null initDefaultLocale = initResponse?.value?.default_lang_code || null
console.log('🚀 ~ initLangCodes:===========', initLangCodes)
} catch (error) { } catch (error) {
console.error('init gameData load error:', error) console.error('init gameData load error:', error)
return
} }
const fullPath = event.path const fullPath = event.path
@@ -247,28 +235,19 @@ export default defineEventHandler(async event => {
initDefaultLocale initDefaultLocale
) )
// 쿼리스트링에서 f 파라미터 값 추출
const path = event?.node.req.url || '' const path = event?.node.req.url || ''
let queryStringF = ''
let fValue = '' let fValue = ''
let test500 = false
if (path.includes('?')) { if (path.includes('?')) {
queryStringF = path.split('?')[1]
// 쿼리스트링에서 f 파라미터 값 추출
try { try {
const urlParams = new URLSearchParams(queryStringF) const queryString = path.split('?')[1]
const urlParams = new URLSearchParams(queryString)
fValue = urlParams.get('f') || '' fValue = urlParams.get('f') || ''
// 테스트용 500 에러 발생 (예: ?test500=true)
test500 = urlParams.get('test500') === 'true'
} catch (e) { } catch (e) {
console.error('쿼리스트링 파싱 에러:', e) console.error('쿼리스트링 파싱 에러:', e)
} }
} }
// 테스트용 500 에러 발생
if (test500) {
throw new Error('테스트용 500 에러 발생')
}
// 미리보기 API 호출 처리 // 미리보기 API 호출 처리
if (fValue === 'preview') { if (fValue === 'preview') {
cleanHost = 'samplegame.onstove.com' cleanHost = 'samplegame.onstove.com'
@@ -278,7 +257,7 @@ export default defineEventHandler(async event => {
game_domain: cleanHost || '', game_domain: cleanHost || '',
lang_code: finalLocale, lang_code: finalLocale,
} }
const response = (await $fetch(apiUrl, { const response = (await $fetch(apiUrl, {
query: queryParams, query: queryParams,
})) as GameDataResponse | null })) as GameDataResponse | null
@@ -288,14 +267,23 @@ export default defineEventHandler(async event => {
setFinalLocaleCookie(event, finalLocale, baseDomain) 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) { if (response?.code === 0 && 'value' in response) {
event.context.gameData = response.value event.context.gameData = response.value
event.context.googleAnalyticsId = response.value?.ga_code event.context.googleAnalyticsId = response.value?.ga_code
console.log('🚀 ~ gameData:', response.value)
// 점검 데이터 조회 // 점검 데이터 조회
let inspectionData let inspectionData: WebInspectionData | undefined
if (cache.has(cacheKey)) { if (cache.has(cacheKey)) {
inspectionData = cache.get(cacheKey) as WebInspectionData inspectionData = cache.get(cacheKey) as WebInspectionData
} else { } else {
@@ -333,50 +321,39 @@ export default defineEventHandler(async event => {
) { ) {
/** /**
* 점검 중인 경우 * 점검 중인 경우
* - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인 * - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인
* - 점검 URL 경로가 아닐 경우 no-cache 설정 * - 점검 URL 경로가 아닐 경우 no-cache 설정
* - 화이트 리스트 체크 * - 화이트 리스트 체크
*/ */
// 현재 경로가 점검 페이지가 아닐 경우 리다이렉트
const inspectionPath = `/${finalLocale}/inspection` const inspectionPath = `/${finalLocale}/inspection`
if (fullPath !== inspectionPath) { const isInspectionPath = fullPath === inspectionPath
// 현재 경로가 점검 페이지가 아닐 경우 캐시 헤더 설정
if (!isInspectionPath) {
setCacheHeaders(event, 'no-cache') setCacheHeaders(event, 'no-cache')
} }
// 응답이 이미 종료되었는지 확인
if (event.node.res.headersSent || event.node.res.writableEnded) {
return
}
// 점검 중일 때 IP 필터링 활성화 여부 확인 // 점검 중일 때 IP 필터링 활성화 여부 확인
if (inspectionData?.ip_filter_use_yn === 'Y') { if (inspectionData?.ip_filter_use_yn === 'Y') {
const clientIP = getTrueClientIp(event.node.req as any) const clientIP = getTrueClientIp(event.node.req as any)
// 점검 중일 때 IP 필터링 활성화 여부 확인 // 허용된 IP 목록 확인
if (inspectionData?.ip_filter_use_yn === 'Y') { if (inspectionData?.ip_filter_list?.includes(clientIP)) {
const clientIP = getTrueClientIp(event.node.req as any) // 화이트 리스트인 경우 Locale Middleware 실행
fnLocaleMiddleware(event, finalLocale)
// 허용된 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)
}
} else { } else {
if (!event.node.res.headersSent && !event.node.res.writableEnded) { // 허용되지 않은 IP인 경우 점검 페이지로 이동
event.node.res.statusCode = 302 event.node.res.statusCode = 302
event.node.res.setHeader('Location', inspectionPath) event.node.res.setHeader('Location', inspectionPath)
event.node.res.end() event.node.res.end()
}
} }
} else { } else {
// IP 필터링이 비활성화된 경우 모든 사용자를 점검 페이지로 이동
event.node.res.statusCode = 302 event.node.res.statusCode = 302
event.node.res.setHeader('Location', inspectionPath) event.node.res.setHeader('Location', inspectionPath)
event.node.res.end() event.node.res.end()
@@ -443,37 +420,40 @@ export default defineEventHandler(async event => {
fnLocaleMiddleware(event, finalLocale) fnLocaleMiddleware(event, finalLocale)
} }
} }
} catch (error) { } catch (error: any) {
console.error('gameData load error:', error) console.error('gameData load error:', error)
// 500 에러 발생 시 /error 페이지로 리다이렉트 // 응답이 이미 종료되었는지 확인
if (!event.node.res.headersSent && !event.node.res.writableEnded) { if (event.node.res.headersSent || event.node.res.writableEnded) {
// 언어 코드 추출 시도 return
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()
} }
// 언어 코드 추출 시도
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,
})
} }
}) })

View File

@@ -12,6 +12,7 @@
".nuxt/nuxt.d.ts", ".nuxt/nuxt.d.ts",
".nuxt/auto-imports.d.ts", ".nuxt/auto-imports.d.ts",
"types/**/*", "types/**/*",
"i18n/**/*",
"layers/**/*", "layers/**/*",
"app/**/*" "app/**/*"
, "temp/inspection.ts", "temp/middleware.ts" ], , "temp/inspection.ts", "temp/middleware.ts" ],

8
types/i18n.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
declare global {
function defineI18nLocale(
loader: (locale: string) => Promise<Record<string, any>> | Record<string, any>
): any
}
export {}