feat: 점검 페이지 추가, 언어 정책 추가
This commit is contained in:
@@ -1,24 +1,5 @@
|
||||
<template>
|
||||
<div class="select-language" :class="{ 'language-changing': isChanging }">
|
||||
<!-- <select
|
||||
v-model="selectedLocale"
|
||||
:disabled="isChanging"
|
||||
class="text-black px-2 py-1 rounded-md"
|
||||
:class="{ 'opacity-50 cursor-not-allowed': isChanging }"
|
||||
@change="switchLanguage"
|
||||
>
|
||||
<option
|
||||
v-for="localeOption in availableLanguages"
|
||||
:key="localeOption"
|
||||
:value="localeOption"
|
||||
>
|
||||
{{ localeOption }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="isChanging" class="ml-2 text-sm text-gray-500">
|
||||
변경 중...
|
||||
</span>
|
||||
-->
|
||||
|
||||
<button
|
||||
:disabled="isChanging"
|
||||
@@ -82,6 +63,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const config = useRuntimeConfig()
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
|
||||
const gameDataStore = useGameDataStore()
|
||||
|
||||
// 사용 가능한 언어 목록
|
||||
@@ -141,12 +125,21 @@ const switchLanguage = async () => {
|
||||
isChanging.value = true
|
||||
|
||||
try {
|
||||
// URL 경로를 통해 언어 변경
|
||||
const path = switchLocalePath(selectedLocale.value as any)
|
||||
if (path) {
|
||||
// 페이지 데이터 초기화 (새로운 언어로 다시 로드되도록)
|
||||
pageDataStore.clearPageData()
|
||||
window.location.href = path
|
||||
// URL 경로를 통해 언어 변경
|
||||
const path = switchLocalePath(selectedLocale.value as any)
|
||||
if (path) {
|
||||
// 언어 쿠키 설정 (클라이언트 사이드) - 페이지 이동 전에 설정
|
||||
const localeCookie = useCookie('LOCALE', {
|
||||
domain: baseDomain,
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위)
|
||||
sameSite: 'lax'
|
||||
})
|
||||
localeCookie.value = selectedLocale.value.toUpperCase()
|
||||
|
||||
// 페이지 데이터 초기화 (새로운 언어로 다시 로드되도록)
|
||||
pageDataStore.clearPageData()
|
||||
window.location.href = path
|
||||
// 언어 변경 및 라우팅
|
||||
// await setLocale(selectedLocale.value as any)
|
||||
|
||||
|
||||
@@ -119,15 +119,10 @@
|
||||
class="nx3 ml-2.5 md:ml-4"
|
||||
>
|
||||
<img
|
||||
src="https://static-pubcomm.gate8.com/local/template/l9/common/logo_nx3.png"
|
||||
:src="`${staticUrl}${setDevCi.dev_ci_img_path}`"
|
||||
alt="CI"
|
||||
class="w-auto h-auto"
|
||||
/>
|
||||
<!-- <img
|
||||
:src="setDevCi.dev_ci_img_path"
|
||||
alt="CI"
|
||||
class="w-auto h-auto"
|
||||
/> -->
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -144,6 +139,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { FooterMenuItem, FooterData, DevCiConfig } from '#layers/types/Common'
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const staticUrl = config.public.staticUrl
|
||||
@@ -163,33 +159,7 @@ const { tm } = useI18n({
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
// 타입 정의
|
||||
interface FooterMenuItem {
|
||||
title: string
|
||||
url: string
|
||||
target?: string
|
||||
active?: string
|
||||
}
|
||||
|
||||
interface GameRatingInfo {
|
||||
company_name: string
|
||||
rating_grade: string
|
||||
reg_no: string
|
||||
prod_date: string
|
||||
rating_class_no: string
|
||||
rating_type: string
|
||||
content_info: string
|
||||
}
|
||||
|
||||
interface FooterData {
|
||||
game_rating_info: GameRatingInfo
|
||||
use_dev_ci_url?: string
|
||||
}
|
||||
|
||||
interface DevCiConfig {
|
||||
dev_ci_yn: boolean
|
||||
dev_ci_img_path: string
|
||||
}
|
||||
const path = ref<string>(`${staticUrl}/local/template/${gameData.value.s3_folder_name}`)
|
||||
|
||||
// 공통다국어 data
|
||||
const footerLinks = computed((): FooterMenuItem[] => {
|
||||
@@ -207,42 +177,43 @@ const getGameRatingImage = computed((): string[] => {
|
||||
const contentInfo = footerData.value.game_rating_info.rating_type.split(',')
|
||||
// rating_type 12, 15, 18, 19 에 따라 이미지명을 가져오고 이미지를 반환
|
||||
return contentInfo.map(item => {
|
||||
if (item === '12') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/Type12.svg`
|
||||
} else if (item === '15') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/Type15.svg`
|
||||
} else if (item === '19') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/Type19.svg`
|
||||
} else if (item === 'all') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/TypeAll.svg`
|
||||
} else if (item === 'e') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/TypeExempt.svg`
|
||||
} else {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_age/TypeTest.svg`
|
||||
switch (item) {
|
||||
case '12':
|
||||
return `${path.value}/common/grades_age/Type12.svg`
|
||||
case '15':
|
||||
return `${path.value}/common/grades_age/Type15.svg`
|
||||
case '19':
|
||||
return `${path.value}/common/grades_age/Type19.svg`
|
||||
case 'all':
|
||||
return `${path.value}/common/grades_age/TypeAll.svg`
|
||||
case 'e':
|
||||
return `${path.value}/common/grades_age/TypeExempt.svg`
|
||||
default:
|
||||
return `${path.value}/common/grades_age/TypeTest.svg`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const getContentInfoImage = computed((): string[] => {
|
||||
const contentInfo = footerData.value.game_rating_info.content_info.split(',')
|
||||
contentInfo.pop()
|
||||
|
||||
return contentInfo.map(item => {
|
||||
if (item === '1') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-sexual.svg`
|
||||
} else if (item === '2') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-fear.svg`
|
||||
} else if (item === '3') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-inapposite.svg`
|
||||
} else if (item === '4') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-drug.svg`
|
||||
} else if (item === '5') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-crime.svg`
|
||||
} else if (item === '6') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-speculation.svg`
|
||||
} else if (item === '7') {
|
||||
return `${staticUrl}/local/template/${gameData.value.s3_folder_name}/common/grades_use/Type-violence.svg`
|
||||
switch (item) {
|
||||
case '1':
|
||||
return `${path.value}/common/grades_use/Type-sexual.svg`
|
||||
case '2':
|
||||
return `${path.value}/common/grades_use/Type-fear.svg`
|
||||
case '3':
|
||||
return `${path.value}/common/grades_use/Type-inapposite.svg`
|
||||
case '4':
|
||||
return `${path.value}/common/grades_use/Type-drug.svg`
|
||||
case '5':
|
||||
return `${path.value}/common/grades_use/Type-crime.svg`
|
||||
case '6':
|
||||
return `${path.value}/common/grades_use/Type-speculation.svg`
|
||||
case '7':
|
||||
return `${path.value}/common/grades_use/Type-violence.svg`
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
export default defineNuxtRouteMiddleware(async (to) => {
|
||||
try {
|
||||
if (import.meta.client) {
|
||||
const gameDataStore = useGameDataStore()
|
||||
const { gameData } = storeToRefs(gameDataStore)
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const baseDomain = `${config.public.baseDomain}`
|
||||
const stoveApiUrl = `${config.public.stoveApiUrl}`
|
||||
console.log("🚀 ~ stoveApiUrl:", stoveApiUrl)
|
||||
// const stoveGameId = `${config.public.stoveGameId}`
|
||||
const stoveGameId = `${gameData.value.game_id}`
|
||||
// const stoveMaintenanceApiUrl = `${config.public.stoveMaintenanceApiUrl}`
|
||||
|
||||
/* const localeCookie = useCookie('LOCALE', {
|
||||
domain: baseDomain
|
||||
}) */
|
||||
// const localeCookie = useCookie('LOCALE', {
|
||||
// domain: baseDomain
|
||||
// })
|
||||
|
||||
// const finalLocale = csrGetFinalLocale(to.path)
|
||||
const finalLocale = csrGetFinalLocale(to.path)
|
||||
console.log("🚀 ~ finalLocale:", finalLocale)
|
||||
// localeCookie.value = finalLocale.toUpperCase()
|
||||
|
||||
// 웹 점검 -----
|
||||
const { isWebInspection, getInspectionDataExternal } = useGetInspectionDataExternal()
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiUrl, gameId: 'STOVE_LORD' })
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiUrl, gameId: stoveGameId })
|
||||
|
||||
// 게임 점검 -----
|
||||
// const { checkGameMaintenance } = useGetGameMaintenance()
|
||||
@@ -30,10 +33,10 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
|
||||
if (isWebInspection.value && !to.path.includes('inspection') && !to.path.includes('api')) {
|
||||
// 점검 중인 경우
|
||||
// return navigateTo(`/${finalLocale}/inspection`, { external: true })
|
||||
return navigateTo(`/${finalLocale}/inspection`, { external: true })
|
||||
} else if (!isWebInspection.value && to.path?.indexOf('inspection') !== -1) {
|
||||
// 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트
|
||||
// return navigateTo(`/${finalLocale}`, { external: true })
|
||||
return navigateTo(`/${finalLocale}`, { external: true })
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
|
||||
try {
|
||||
if(to.matched) {
|
||||
if(to.path.includes('inspection')) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -41,12 +41,12 @@ export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
})) as PageDataResponse | null
|
||||
|
||||
console.log("🚀 ~ response?.code:", response?.code)
|
||||
if(response?.code === 91003) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Page not found',
|
||||
})
|
||||
}
|
||||
// if(response?.code === 91003) {
|
||||
// throw createError({
|
||||
// statusCode: 404,
|
||||
// statusMessage: 'Page not found',
|
||||
// })
|
||||
// }
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
store.setPageData(response.value)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import {
|
||||
getHeader,
|
||||
getRequestHost,
|
||||
@@ -7,28 +8,179 @@ import {
|
||||
import { ssrGetFinalLocale } from '../../utils/localeUtil'
|
||||
import type { GameDataResponse } from '../../types/api/gameData'
|
||||
import type { ResGetInspectionData } from '../../types/InspectionType'
|
||||
import { isStaticFile } from '#layers/utils/commonUtil'
|
||||
|
||||
/**
|
||||
* 캐시 제어 헤더를 설정하는 공통 함수
|
||||
*
|
||||
* @param event - 이벤트 객체
|
||||
* @param cacheMode - 캐시 모드 설정 ('no-cache', 'short', 'medium', 'default')
|
||||
* @param customMaxAge - 커스텀 max-age 값 (초 단위)
|
||||
*/
|
||||
function setCacheHeaders(
|
||||
event: { node: { res: { setHeader: (name: string, value: string) => void } } },
|
||||
cacheMode: 'no-cache' | 'short' | 'medium' | 'default',
|
||||
customMaxAge?: number
|
||||
): void {
|
||||
// 원래 setHeader 함수 참조 저장
|
||||
const originalSetHeader = event.node.res.setHeader
|
||||
|
||||
// Cache-Control 헤더 설정값 결정
|
||||
let cacheControl: string
|
||||
switch (cacheMode) {
|
||||
case 'no-cache':
|
||||
cacheControl = 'no-cache, no-store, must-revalidate'
|
||||
// no-cache 모드일 때는 추가 헤더도 설정
|
||||
event.node.res.setHeader('Pragma', 'no-cache')
|
||||
event.node.res.setHeader('Expires', '0')
|
||||
break
|
||||
case 'short':
|
||||
cacheControl = `public, max-age=${customMaxAge || 10}`
|
||||
break
|
||||
case 'medium':
|
||||
cacheControl = `public, max-age=${customMaxAge || 15}`
|
||||
break
|
||||
case 'default':
|
||||
default:
|
||||
cacheControl = `public, max-age=${customMaxAge || 60}`
|
||||
break
|
||||
}
|
||||
|
||||
// Cache-Control 헤더를 강제로 설정하기 위해 setHeader 메소드 오버라이드
|
||||
event.node.res.setHeader = function (name: string, value: string) {
|
||||
if (name.toLowerCase() === 'cache-control') {
|
||||
return originalSetHeader.call(this, name, cacheControl)
|
||||
}
|
||||
return originalSetHeader.call(this, name, value)
|
||||
}
|
||||
|
||||
// 바로 캐시 제어 헤더 적용
|
||||
event.node.res.setHeader('Cache-Control', cacheControl)
|
||||
}
|
||||
|
||||
const cache = new LRUCache({
|
||||
max: 100, // 캐시에 저장할 최대 항목 수
|
||||
ttl: 1000 * 30 // 30초 동안 캐시 유지
|
||||
})
|
||||
|
||||
/**
|
||||
* 최종 언어 쿠키 세팅
|
||||
*
|
||||
* @param event - 이벤트 객체
|
||||
* @param finalLocale - 최종 언어
|
||||
* @param baseDomain - 기본 도메인
|
||||
*/
|
||||
function setFinalLocaleCookie(event: any, finalLocale: string, baseDomain: string) {
|
||||
setCookie(event, 'LOCALE', finalLocale.toUpperCase(), {
|
||||
domain: baseDomain,
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 365 // 1년 (초 단위)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale Middleware 역할 함수
|
||||
*
|
||||
* @param event - 이벤트 객체
|
||||
* @param finalLocale - 최종 언어
|
||||
*/
|
||||
function fnLocaleMiddleware(event: any, finalLocale: string) {
|
||||
const path = event?.node.req.url || ''
|
||||
let arrPath = []
|
||||
let queryString = ''
|
||||
|
||||
if (path.includes('?')) {
|
||||
// 쿼리스트링 포함 시 순수 경로만 추출
|
||||
arrPath = path.split('?')[0].split('/')
|
||||
queryString = path.split('?')[1]
|
||||
} else {
|
||||
arrPath = path.split('/')
|
||||
queryString = ''
|
||||
}
|
||||
|
||||
// 최종 언어 세팅된 경로 생성
|
||||
const pathLocale = arrPath.length > 1 ? arrPath[1] : ''
|
||||
|
||||
// URL에서 현재 언어와 최종 언어가 다르면 리다이렉트
|
||||
if (pathLocale !== finalLocale) {
|
||||
let newLocalePath = ''
|
||||
if (pathLocale === '') {
|
||||
newLocalePath = `/${finalLocale}`
|
||||
} else {
|
||||
arrPath[1] = finalLocale
|
||||
newLocalePath = arrPath.join('/')
|
||||
}
|
||||
|
||||
if (queryString !== '') {
|
||||
newLocalePath += `?${queryString}`
|
||||
}
|
||||
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', newLocalePath)
|
||||
event.node.res.end()
|
||||
}
|
||||
}
|
||||
|
||||
export default defineEventHandler(async event => {
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const runType = `${config.public.runType}`
|
||||
const iBaseApiUrl = `${config.public.stoveApiUrlServer}`
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
console.log("🚀 ~ baseDomain:", config.public.baseDomain)
|
||||
// const url = getRequestURL(event)
|
||||
|
||||
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 fullPath = event.path
|
||||
|
||||
// 1-1. 정적 파일 패스
|
||||
if (isStaticFile(event.path)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 1-2. /inspection 패스
|
||||
if (fullPath.includes('/inspection')) {
|
||||
// 리턴 되기 전 언어 쿠키 세팅
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
console.log("🚀 0000 ~ finalLocale:", finalLocale)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
return
|
||||
}
|
||||
|
||||
// 정적 자산, API, 파비콘 등은 제외하고 페이지 요청만 처리
|
||||
if (
|
||||
url.pathname.startsWith('/api/') ||
|
||||
url.pathname.startsWith('/_nuxt/') ||
|
||||
url.pathname.startsWith('/favicon') ||
|
||||
url.pathname.includes('.') ||
|
||||
url.pathname.startsWith('/_')
|
||||
fullPath.startsWith('/api/') ||
|
||||
fullPath.startsWith('/_nuxt/') ||
|
||||
fullPath.startsWith('/favicon') ||
|
||||
fullPath.includes('/assets/') ||
|
||||
fullPath.includes('.') ||
|
||||
fullPath.startsWith('/_')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 캐시 키 생성
|
||||
const cacheKey = 'inspection'
|
||||
console.log("🚀 11111 ~ cacheKey:", cacheKey)
|
||||
|
||||
const host =
|
||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
||||
const baseDomain = process.env.BASE_DOMAIN || '.onstove.com'
|
||||
const isGameDomainExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameDomainExtractable) {
|
||||
@@ -41,42 +193,66 @@ export default defineEventHandler(async event => {
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiUrlServer = config.public.stoveApiUrlServer
|
||||
const apiUrl = `${stoveApiUrlServer}/pub-comm/v1.0/template/game`
|
||||
let inspectionData
|
||||
const langCode = ssrGetFinalLocale(
|
||||
event?.node.req.url,
|
||||
event.node.req.headers
|
||||
)
|
||||
|
||||
|
||||
// 2. 언어 코드 추출
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
game_domain: event.context.gameDomain || '',
|
||||
lang_code: langCode,
|
||||
lang_code: finalLocale,
|
||||
}
|
||||
|
||||
const response = (await $fetch(apiUrl, {
|
||||
query: queryParams,
|
||||
})) as GameDataResponse | null
|
||||
|
||||
// 언어패스 쿠키 굽기 - 장기방안에서는 굽지않음
|
||||
// const langCoverages = response?.value?.lang_codes || []
|
||||
// if(langCoverages.includes(finalLocale)) {
|
||||
// setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
// }
|
||||
|
||||
if (response?.code === 0 && 'value' in response) {
|
||||
event.context.gameData = response.value
|
||||
event.context.googleAnalyticsId = response.value?.ga_code
|
||||
|
||||
console.log('🚀 ~ gameData:', response.value)
|
||||
|
||||
|
||||
// 점검 데이터 조회
|
||||
if (response.value.game_id) {
|
||||
const inspectionApiUrl = `${iBaseApiUrl}/pub-comm/v3.0/inspection/${response.value.game_id}`
|
||||
|
||||
// 직접 $fetch 사용 (composable 사용하지 않음)
|
||||
const inspectionResponse = await $fetch<ResGetInspectionData>(inspectionApiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
inspectionData = inspectionResponse?.value?.inspection
|
||||
// console.log("🚀 ~ inspectionData:", inspectionData)
|
||||
let inspectionData
|
||||
if (cache.has(cacheKey)) {
|
||||
inspectionData = cache.get(cacheKey) as WebInspectionData
|
||||
// console.log("🚀 22222 ~ 캐시키 있어 inspectionData:", inspectionData)
|
||||
} else {
|
||||
// 점검 데이터 조회
|
||||
if (response.value.game_id) {
|
||||
const inspectionApiUrl = `${iBaseApiUrl}/pub-comm/v3.0/inspection/${response.value.game_id}`
|
||||
// console.log("🚀 ~ inspectionApiUrl:", inspectionApiUrl)
|
||||
|
||||
// 직접 $fetch 사용 (composable 사용하지 않음)
|
||||
const inspectionResponse = await $fetch<ResGetInspectionData>(inspectionApiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
inspectionData = inspectionResponse?.value?.inspection
|
||||
cache.set(cacheKey, inspectionData) // 캐시에 저장
|
||||
// console.log("🚀 ~ inspectionData:", inspectionData)
|
||||
|
||||
}
|
||||
|
||||
if (inspectionData?.inspection_status === 0) {
|
||||
|
||||
// 4. 현재 시간과 점검 기간 비교
|
||||
const currentTime = Date.now()
|
||||
const tsStartDate = inspectionData?.ts_start_date || 0
|
||||
const tsEndDate = inspectionData?.ts_end_date || 0
|
||||
const timeUntilInspectionSeconds = Math.floor((tsStartDate - currentTime) / 1000)
|
||||
|
||||
|
||||
// 5. 점검 상태별 캐시 설정
|
||||
if (inspectionData?.inspection_status === 1 && currentTime >= tsStartDate && currentTime <= tsEndDate) {
|
||||
/**
|
||||
* 점검 중인 경우
|
||||
* - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ
|
||||
@@ -84,18 +260,78 @@ export default defineEventHandler(async event => {
|
||||
* - 화이트 리스트 체크
|
||||
*/
|
||||
// 현재 경로가 점검 페이지가 아닐 경우 리다이렉트
|
||||
const inspectionPath = `/${langCode}/inspection`
|
||||
|
||||
if (!url.pathname.includes('/inspection')) {
|
||||
const inspectionPath = `/${finalLocale}/inspection`
|
||||
if (fullPath !== inspectionPath) {
|
||||
setCacheHeaders(event, 'no-cache')
|
||||
}
|
||||
|
||||
// 점검 중일 때 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인 경우 점검 페이지로 이동
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', inspectionPath)
|
||||
event.node.res.end()
|
||||
} else {
|
||||
// 화이트 리스트인 경우
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
}
|
||||
} else {
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', inspectionPath)
|
||||
event.node.res.end()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* 점검이 아닌 경우
|
||||
* - 홈 경로는 no-cache
|
||||
* - 점검 예정 시간에 따른 캐시 설정
|
||||
* - 점검 5분 전: 짧은 캐시 (10초)
|
||||
* - 점검 30분 전: 중간 캐시 (15초)
|
||||
* - 점검 30분 이후: 기본 캐시 (60초)
|
||||
*/
|
||||
// 홈 경로: 캐시 없음
|
||||
const isHomePath = [
|
||||
'',
|
||||
'/'
|
||||
//, ...Object.values(DEFAULT_LOCALE_COVERAGES).flatMap((locale) => [`/${locale}`, `/${locale}/`])
|
||||
].includes(fullPath)
|
||||
|
||||
if (isHomePath) {
|
||||
setCacheHeaders(event, 'no-cache')
|
||||
} else {
|
||||
// 점검 예정 시간에 따른 캐시 설정
|
||||
|
||||
if (tsStartDate > 0 && timeUntilInspectionSeconds > 0) {
|
||||
if (timeUntilInspectionSeconds < 300) {
|
||||
// 점검 5분 전: 짧은 캐시 (10초)
|
||||
setCacheHeaders(event, 'short', 10)
|
||||
} else if (timeUntilInspectionSeconds < 1800) {
|
||||
// 점검 30분 전: 중간 캐시 (15초)
|
||||
setCacheHeaders(event, 'medium', 15)
|
||||
} else {
|
||||
// 점검 30분 이후: 기본 캐시 (60초)
|
||||
setCacheHeaders(event, 'default')
|
||||
}
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('gameData load error:', error)
|
||||
|
||||
68
layers/server/plugins/nitroPlugin.ts
Normal file
68
layers/server/plugins/nitroPlugin.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { RenderResponse } from 'nitropack'
|
||||
import type { H3Event } from 'h3'
|
||||
import { defineNitroPlugin } from 'nitropack/runtime'
|
||||
import { getTrueClientIp } from '#layers/utils/apiUtil'
|
||||
|
||||
function generateRequestId(): string {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2)
|
||||
}
|
||||
|
||||
function getIpAddress(event: H3Event): string {
|
||||
return getTrueClientIp(event.node.req as any) || 'unknown'
|
||||
}
|
||||
|
||||
export default defineNitroPlugin((nitroApp) => {
|
||||
// 정적 파일 체크 함수 추가
|
||||
const isStaticFile = (path: string): boolean => {
|
||||
return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/i.test(path)
|
||||
}
|
||||
|
||||
// 헬스체크 경로 체크 함수 추가
|
||||
const isHealthCheck = (path: string): boolean => {
|
||||
return path === '/health'
|
||||
}
|
||||
|
||||
nitroApp.hooks.hook('request', (event) => {
|
||||
// 정적 파일 요청은 로깅 제외
|
||||
if (isStaticFile(event.path) || isHealthCheck(event.path)) {
|
||||
return
|
||||
}
|
||||
// 상세 로깅을 위한 정보 수집
|
||||
const startTime = Date.now()
|
||||
const userAgent = event.node.req.headers['user-agent'] || ''
|
||||
const method = event.method || ''
|
||||
const headers = JSON.stringify(event.node.req.headers, null, 2)
|
||||
const requestId = generateRequestId()
|
||||
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
console.log(
|
||||
`Request Info {"requestId":"${requestId}", "type":"request","method":"${method}","url":"${event.path}","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}", "headers" : "${headers}" }`
|
||||
)
|
||||
|
||||
// 요청 완료 후 응답 상태 코드 로깅
|
||||
event.node.res.on('finish', () => {
|
||||
console.log(
|
||||
`Response Info {"requestId":"${requestId}","type":"response","method":"${method}","url":"${event.path}","statusCode":${event.node.res.statusCode},"responseTime":"${Date.now() - startTime}ms","userIp":"${getIpAddress(event)}","userAgent":"${userAgent}","statusMessage":"${event.node.res.statusMessage}","responseHeader": ${JSON.stringify(event.node.res.getHeaders(), null, 2)}}`
|
||||
)
|
||||
console.log(
|
||||
'==========================================================================================================================================================================================================================================================='
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
nitroApp.hooks.hook('error', (error) => {
|
||||
console.error('[Nitro Error]', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
})
|
||||
|
||||
// 응답 헤더에서 'x-powered-by' 제거
|
||||
nitroApp.hooks.hook('render:response', (response: Partial<RenderResponse>) => {
|
||||
if (response?.headers) {
|
||||
delete response.headers['x-powered-by']
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -27,4 +27,33 @@ interface ParsedCustomLinkOptions {
|
||||
query?: Record<string, any>
|
||||
}
|
||||
|
||||
export type { CommonRequestType, CommonResponseType, CommonPeriodType, ParsedCustomLinkOptions }
|
||||
|
||||
// 타입 정의
|
||||
interface FooterMenuItem {
|
||||
title: string
|
||||
url: string
|
||||
target?: string
|
||||
active?: string
|
||||
}
|
||||
|
||||
interface GameRatingInfo {
|
||||
company_name: string
|
||||
rating_grade: string
|
||||
reg_no: string
|
||||
prod_date: string
|
||||
rating_class_no: string
|
||||
rating_type: string
|
||||
content_info: string
|
||||
}
|
||||
|
||||
interface FooterData {
|
||||
game_rating_info: GameRatingInfo
|
||||
use_dev_ci_url?: string
|
||||
}
|
||||
|
||||
interface DevCiConfig {
|
||||
dev_ci_yn: boolean
|
||||
dev_ci_img_path: string
|
||||
}
|
||||
|
||||
export type { CommonRequestType, CommonResponseType, CommonPeriodType, ParsedCustomLinkOptions, FooterMenuItem, GameRatingInfo, FooterData, DevCiConfig }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DEFAULT_LOCALE_CODE, DEFAULT_COVERAGES } from '@/i18n.config'
|
||||
import { DEFAULT_LOCALE_CODE } from '../../i18n.config'
|
||||
|
||||
// 사용자 선호 언어 가져오기
|
||||
// 사용자 선호 언어 조회
|
||||
export const getPreferredLanguage = (acceptLanguageHeader = '') => {
|
||||
const languages = acceptLanguageHeader
|
||||
.split(',')
|
||||
@@ -13,58 +13,18 @@ export const getPreferredLanguage = (acceptLanguageHeader = '') => {
|
||||
return languages.length > 0 ? languages[0].code : null
|
||||
}
|
||||
|
||||
export const getFinalLanguage = (path = '', defaultLocale: string, coverages: string[]) => {
|
||||
// const nuxtApp = useNuxtApp()
|
||||
let finalLocale = ''
|
||||
let requestedLocale
|
||||
let acceptLanguage: string
|
||||
let defaultLang = 'en'
|
||||
let defaultLangEn: string
|
||||
if (defaultLocale) {
|
||||
defaultLangEn = defaultLocale
|
||||
} else {
|
||||
defaultLangEn = 'en'
|
||||
// 쿠키 파싱 유틸리티 함수
|
||||
const parseCookies = (cookieHeader: string) => {
|
||||
const cookies: Record<string, string> = {}
|
||||
if (cookieHeader) {
|
||||
cookieHeader.split(';').forEach((cookie) => {
|
||||
const [name, value] = cookie.trim().split('=')
|
||||
if (name && value) {
|
||||
cookies[name] = decodeURIComponent(value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
requestedLocale = path?.split('/')[1]?.toLowerCase() ?? 'undefined'
|
||||
|
||||
if (import.meta.server) {
|
||||
const headers = useRequestHeaders(['accept-language'])
|
||||
acceptLanguage = headers['accept-language'] || defaultLangEn
|
||||
|
||||
defaultLang =
|
||||
coverages.find((locale: string) => getPreferredLanguage(acceptLanguage)?.startsWith(locale)) || defaultLangEn
|
||||
}
|
||||
|
||||
// const DEFAULT_COVERAGES = i18n.locales.map((locale) => locale.code)
|
||||
const DEFAULT_COVERAGES = coverages
|
||||
const requestedPage = path?.split('/')[2]?.toLowerCase() ?? undefined
|
||||
|
||||
const localeMap: Record<string, string> = {
|
||||
'zh-tw': 'zh-TW',
|
||||
'zh-cn': 'zh-CN'
|
||||
}
|
||||
|
||||
if (localeMap[requestedLocale]) {
|
||||
requestedLocale = localeMap[requestedLocale]
|
||||
}
|
||||
|
||||
if (requestedLocale !== undefined && DEFAULT_COVERAGES.includes(requestedLocale)) {
|
||||
finalLocale = requestedLocale
|
||||
} else if (
|
||||
requestedLocale === undefined ||
|
||||
requestedLocale === '' ||
|
||||
path !== '' ||
|
||||
(requestedLocale !== undefined && !DEFAULT_COVERAGES.includes(requestedLocale) && requestedPage !== undefined)
|
||||
) {
|
||||
// 요청된 언어가 없을 때 or 잘못된 언어코드로 요청 시 브라우저 언어로 설정
|
||||
finalLocale = defaultLang
|
||||
} else {
|
||||
// 그 외의 경우 기본 언어로 설정 (중국어 번체)
|
||||
finalLocale = defaultLangEn
|
||||
}
|
||||
|
||||
return finalLocale.toLowerCase()
|
||||
return cookies
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,34 +33,38 @@ export const getFinalLanguage = (path = '', defaultLocale: string, coverages: st
|
||||
* @param {string} path - 현재 URL 경로
|
||||
*/
|
||||
export const csrGetFinalLocale = (path = '') => {
|
||||
const config = useRuntimeConfig()
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
const localeMap: Record<string, string> = {
|
||||
'zh-tw': 'zh-TW',
|
||||
'zh-cn': 'zh-CN'
|
||||
}
|
||||
|
||||
// 1. URL 패스에 포함된 언어
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
const pathLocal = path.split('/')[1]
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
if (path.includes('?')) {
|
||||
path = path.split('?')[0]
|
||||
}
|
||||
const pathLocale = `${path.split('/')[1]}`.toLowerCase()
|
||||
|
||||
// URL 패스에 포함된 언어가 지원하는 언어인지 체크
|
||||
if (pathLocal && pathLocal !== '' && DEFAULT_COVERAGES.includes(pathLocal)) {
|
||||
finalLocale = pathLocal // .toLowerCase()
|
||||
|
||||
if (localeMap[pathLocal]) {
|
||||
finalLocale = localeMap[pathLocal]
|
||||
}
|
||||
if (pathLocale && pathLocale !== '') {
|
||||
finalLocale = pathLocale
|
||||
return finalLocale
|
||||
}
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 2. 브라우저 언어
|
||||
const browserLanguage = navigator.language || navigator.languages[0]
|
||||
if (browserLanguage && browserLanguage !== '' && DEFAULT_COVERAGES.includes(browserLanguage)) {
|
||||
finalLocale = browserLanguage // .toLowerCase()
|
||||
if (localeMap[browserLanguage]) {
|
||||
finalLocale = localeMap[browserLanguage]
|
||||
}
|
||||
// 2. LOCALE 쿠키 언어
|
||||
const cookieLanguage = `${useCookie('LOCALE', { domain: baseDomain }).value}`.toLowerCase()
|
||||
if (cookieLanguage && cookieLanguage !== '') {
|
||||
finalLocale = cookieLanguage
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 3. 브라우저 언어
|
||||
const browserLanguage = `${navigator.language || navigator.languages[0]}`.toLowerCase()
|
||||
if (browserLanguage && browserLanguage !== '' && coveragesLocales.includes(browserLanguage)) {
|
||||
finalLocale = browserLanguage
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
@@ -118,22 +82,31 @@ export const csrGetFinalLocale = (path = '') => {
|
||||
*/
|
||||
export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
|
||||
|
||||
try {
|
||||
// 1. URL path에 포함된 언어 정보
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
const pathLocale = path.split('/')[1]
|
||||
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
if (path.includes('?')) {
|
||||
path = path.split('?')[0]
|
||||
}
|
||||
const pathLocalee = `${path.split('/')[1]}`.toLowerCase()
|
||||
// URL path에 포함된 언어 정보가 지원하는 언어인지 체크
|
||||
if (pathLocale && pathLocale !== '' && DEFAULT_COVERAGES.includes(pathLocale)) {
|
||||
finalLocale = pathLocale // .toLowerCase()
|
||||
|
||||
if (pathLocalee && pathLocalee !== '') {
|
||||
finalLocale = pathLocalee
|
||||
return finalLocale
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 요청 헤더의 브라우저 언어 (accept-language)
|
||||
// 2. LOCALE 쿠키 언어 (SSR에서는 headers에서 직접 파싱)
|
||||
const cookieHeader = headers.cookie || ''
|
||||
const cookies = parseCookies(cookieHeader)
|
||||
const cookieLanguage = cookies.LOCALE ? `${cookies.LOCALE}`.toLowerCase() : ''
|
||||
if (cookieLanguage && cookieLanguage !== '' && coveragesLocales.includes(cookieLanguage)) {
|
||||
finalLocale = cookieLanguage
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 3. 요청 헤더의 브라우저 언어 (accept-language)
|
||||
if (headers && headers['accept-language']) {
|
||||
const acceptLanguage = Array.isArray(headers['accept-language'])
|
||||
? headers['accept-language'][0]
|
||||
@@ -143,13 +116,11 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
const preferredLocale = getPreferredLanguage(acceptLanguage)
|
||||
if (preferredLocale) {
|
||||
// 선호 언어의 기본 코드와 일치하는 지원 로케일 찾기
|
||||
const matchedLocale = DEFAULT_COVERAGES.find((locale: string) =>
|
||||
const matchedLocale = coveragesLocales.find((locale: string) =>
|
||||
preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
|
||||
)
|
||||
if (matchedLocale) {
|
||||
finalLocale = matchedLocale
|
||||
// return matchedLocale.toLowerCase()
|
||||
|
||||
finalLocale = matchedLocale.toLowerCase()
|
||||
return finalLocale
|
||||
}
|
||||
}
|
||||
@@ -159,7 +130,6 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
// 3. 서비스 기본 언어
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
} catch (e) {
|
||||
console.error('[Exception] localeUtil.ssrGetFinalLocale: ', e)
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
}
|
||||
return finalLocale
|
||||
|
||||
Reference in New Issue
Block a user