diff --git a/app/pages/inspection/index.vue b/app/pages/inspection/index.vue index 0841163..e2f1491 100644 --- a/app/pages/inspection/index.vue +++ b/app/pages/inspection/index.vue @@ -1,28 +1,39 @@ \ No newline at end of file + diff --git a/layers/composables/useGds.ts b/layers/composables/useGds.ts index 646b003..1d03d86 100644 --- a/layers/composables/useGds.ts +++ b/layers/composables/useGds.ts @@ -66,8 +66,8 @@ const useGds = () => { }, } as ResGetGdsClientPolicyTotal } else { - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const url = `${stoveApiBaseUrl}/gds/v2/client/policy/total` const query = { diff --git a/layers/composables/useGetGameDataExternal.ts b/layers/composables/useGetGameDataExternal.ts index 9d79789..a891439 100644 --- a/layers/composables/useGetGameDataExternal.ts +++ b/layers/composables/useGetGameDataExternal.ts @@ -13,9 +13,8 @@ export const useGetGameDataExternal = () => { const getGameDataExternal = async (req: GameDataRequest) => { console.log('🚀 ~ getGameDataExternal ~ req:', req) - // const config = useRuntimeConfig() - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}` try { @@ -23,7 +22,7 @@ export const useGetGameDataExternal = () => { console.log('🚀 ~ getGameDataExternal ~ response:', response) // FIXME: 테스트용 데이터 --------------------------------------------------- - /* if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) { + /* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) { response.value = { inspection_status: 1, inspection: { diff --git a/layers/composables/useGetGameMaintenance.ts b/layers/composables/useGetGameMaintenance.ts index a9835b3..4e35bcc 100644 --- a/layers/composables/useGetGameMaintenance.ts +++ b/layers/composables/useGetGameMaintenance.ts @@ -1,4 +1,7 @@ -import type { ReqGameMaintenance, ResGameMaintenance } from '#layers/types/GameMaintenanceType' +import type { + ReqGameMaintenance, + ResGameMaintenance, +} from '#layers/types/GameMaintenanceType' /** * 게임 점검 @@ -7,7 +10,7 @@ const useGetGameMaintenance = () => { const inspectionStore = useInspectionStore() const logPrefix = { exception: '[Exception] /composables/useGetGameMaintenance', - failure: '[Failure] /composables/useGetGameMaintenance' + failure: '[Failure] /composables/useGetGameMaintenance', } const isGameMaintenance = ref(false) // 게임 서버 점검 여부 @@ -20,7 +23,11 @@ const useGetGameMaintenance = () => { const setGameMaintenanceFalse = () => { setIsGameMaintenance(false) inspectionStore.setGameMaintenanceStatus(false) - inspectionStore.setGameMaintenanceData({ ts_start_date: 0, ts_end_date: 0, detail_link: '' }) + inspectionStore.setGameMaintenanceData({ + ts_start_date: 0, + ts_end_date: 0, + detail_link: '', + }) } /** @@ -45,8 +52,8 @@ const useGetGameMaintenance = () => { if (res != null && res.code === 0) { // FIXME: 테스트용 데이터 --------------------------------------------------- - /* const config = useRuntimeConfig() - if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) { + /* const runtimeConfig = useRuntimeConfig() + if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) { res.value = { total_count: 1, list: [ @@ -65,12 +72,16 @@ const useGetGameMaintenance = () => { } } */ // ------------------------------------------------------------------------ - if (Number(res.value?.total_count) > 0 && res.value?.list != null && res.value?.list.length > 0) { + if ( + Number(res.value?.total_count) > 0 && + res.value?.list != null && + res.value?.list.length > 0 + ) { setIsGameMaintenance(true) // 서버 1개 이상 점검일 경우 점검 중으로 간주 inspectionStore.setGameMaintenanceData({ ts_start_date: res.value?.list[0].start_at || 0, ts_end_date: res.value?.list[0].end_at || 0, - detail_link: res.value?.list[0].languages[0].link || '' + detail_link: res.value?.list[0].languages[0].link || '', }) inspectionStore.setGameMaintenanceStatus(true) } else { diff --git a/layers/composables/useGetInspectionDataExternal.ts b/layers/composables/useGetInspectionDataExternal.ts index c487a46..2801eff 100644 --- a/layers/composables/useGetInspectionDataExternal.ts +++ b/layers/composables/useGetInspectionDataExternal.ts @@ -1,4 +1,8 @@ -import type { WebInspectionData, ReqGetInspectionData, ResGetInspectionData } from '#layers/types/InspectionType' +import type { + WebInspectionData, + ReqGetInspectionData, + ResGetInspectionData, +} from '#layers/types/InspectionType' /** * 웹 점검 @@ -7,7 +11,7 @@ export const useGetInspectionDataExternal = () => { const inspectionStore = useInspectionStore() const logPrefix = { exception: '[Exception] /composables/useGetInspectionDataExternal', - failure: '[Failure] /composables/useGetInspectionDataExternal' + failure: '[Failure] /composables/useGetInspectionDataExternal', } const webInspectionData = ref(null) const isWebInspection = ref(false) // 웹 점검 여부 @@ -24,15 +28,18 @@ export const useGetInspectionDataExternal = () => { * @description https://wiki.smilegate.net/pages/viewpage.action?pageId=563198067 */ const getInspectionDataExternal = async (req: ReqGetInspectionData) => { - // const config = useRuntimeConfig() + // const runtimeConfig = useRuntimeConfig() const apiUrl = `${req.baseApiUrl}/pub-comm/v3.0/inspection/${req.gameId}` try { - const response = (await commonFetch('GET', apiUrl)) as ResGetInspectionData - console.log("🚀 ~ getInspectionDataExternal ~ response:", response) + const response = (await commonFetch( + 'GET', + apiUrl + )) as ResGetInspectionData + console.log('🚀 ~ getInspectionDataExternal ~ response:', response) // FIXME: 테스트용 데이터 --------------------------------------------------- - /* if (['local', 'local-gate8', 'dev'].includes(`${config.public.runType}`)) { + /* if (['local', 'local-gate8', 'dev'].includes(`${runtimeConfig.public.runType}`)) { response.value = { inspection_status: 1, inspection: { diff --git a/layers/composables/usePreregist.ts b/layers/composables/usePreregist.ts index 6b40b8f..53d614d 100644 --- a/layers/composables/usePreregist.ts +++ b/layers/composables/usePreregist.ts @@ -37,8 +37,8 @@ const usePreregist = () => { const getPreregist = async (req: ReqPreorderSelectEvent) => { let res: ResPreorderSelectEvent = {} as ResPreorderSelectEvent try { - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/SelectEvent` const headers = { @@ -99,8 +99,8 @@ const usePreregist = () => { const setPreregist = async (req: ReqPreorderReserveDataUpdate) => { let res: ResPreorderReserveDataUpdate = {} as ResPreorderReserveDataUpdate try { - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const url = `${stoveApiBaseUrl}/pub-comm/v1.0/Preorder/ReserveDataUpdate` const headers = { diff --git a/layers/composables/useResourcesData.ts b/layers/composables/useResourcesData.ts index b7d9a3a..e5e81ae 100644 --- a/layers/composables/useResourcesData.ts +++ b/layers/composables/useResourcesData.ts @@ -19,8 +19,8 @@ export const useResourcesData = () => { ): Promise => { const { pageSeq, pageVer, pageVerTmplSeq, langCode, q, qc } = params - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/operateResources` const queryParams: Record = { @@ -68,8 +68,8 @@ export const useResourcesData = () => { size, } = params - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const apiUrl = `${stoveApiBaseUrl}/cwms/v3.0/article_group/${articleGroupCode}/${articleGroupSeq}/article/list` const queryParams: Record = { diff --git a/layers/middleware/init.route.global.ts b/layers/middleware/init.route.global.ts index 9c2559d..25ec6f0 100644 --- a/layers/middleware/init.route.global.ts +++ b/layers/middleware/init.route.global.ts @@ -14,14 +14,14 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { const currentLangCode = match ? match[1] : null //현재 url에서 게임 도메인만 추출 - const currentDomain = window.location.hostname; - const config = useRuntimeConfig() + const currentDomain = window.location.hostname + const runtimeConfig = useRuntimeConfig() const req: GameDataRequest = { gameDomain: `${currentDomain}`, langCode: `${currentLangCode}`, game_alias: '', lang_code: `${currentLangCode}`, - baseApiUrl: `${config.public.stoveApiUrl}`, + baseApiUrl: `${runtimeConfig.public.stoveApiUrl}`, gameId: '', } const { getGameDataExternal } = useGetGameDataExternal() @@ -30,7 +30,7 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { const gameDataStore = useGameDataStore() const gameData = gameDataStore.gameData as GameDataValue const langCodes = gameData?.lang_codes - + // 허용된 언어 코드 목록 const allowedLangCodes = langCodes || [] diff --git a/layers/middleware/inspection.ts b/layers/middleware/inspection.ts index 36ccf8d..77ef40e 100644 --- a/layers/middleware/inspection.ts +++ b/layers/middleware/inspection.ts @@ -1,15 +1,15 @@ -export default defineNuxtRouteMiddleware(async (to) => { +export default defineNuxtRouteMiddleware(async to => { try { if (import.meta.client) { const gameDataStore = useGameDataStore() const { gameData } = storeToRefs(gameDataStore) - console.log("🚀 ~ 00000 gameData:", gameData.value) + console.log('🚀 ~ 00000 gameData:', gameData.value) - const config = useRuntimeConfig() - // const baseDomain = `${config.public.baseDomain}` - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + // const baseDomain = `${runtimeConfig.public.baseDomain}` + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const stoveGameId = gameData.value.game_id - // const stoveMaintenanceApiUrl = `${config.public.stoveMaintenanceApiUrl}` + // const stoveMaintenanceApiUrl = `${runtimeConfig.public.stoveMaintenanceApiUrl}` // const localeCookie = useCookie('LOCALE', { // domain: baseDomain @@ -19,9 +19,12 @@ export default defineNuxtRouteMiddleware(async (to) => { // localeCookie.value = finalLocale.toUpperCase() // 웹 점검 ----- - const { isWebInspection, getInspectionDataExternal } = useGetInspectionDataExternal() - await getInspectionDataExternal({ baseApiUrl: stoveApiBaseUrl, gameId: stoveGameId }) - + const { isWebInspection, getInspectionDataExternal } = + useGetInspectionDataExternal() + await getInspectionDataExternal({ + baseApiUrl: stoveApiBaseUrl, + gameId: stoveGameId, + }) // 게임 점검 ----- // const { checkGameMaintenance } = useGetGameMaintenance() @@ -32,10 +35,17 @@ export default defineNuxtRouteMiddleware(async (to) => { // lang: `${finalLocale}`.toLowerCase() // }) - if (isWebInspection.value && !to.path.includes('inspection') && !to.path.includes('api')) { + if ( + isWebInspection.value && + !to.path.includes('inspection') && + !to.path.includes('api') + ) { // 점검 중인 경우 return navigateTo(`/${finalLocale}/inspection`, { external: true }) - } else if (!isWebInspection.value && to.path?.indexOf('inspection') !== -1) { + } else if ( + !isWebInspection.value && + to.path?.indexOf('inspection') !== -1 + ) { // 점검이 종료된 후 점검 페이지 접근시 메인으로 리다이렉트 return navigateTo(`/${finalLocale}`, { external: true }) } diff --git a/layers/middleware/pageData.global.ts b/layers/middleware/pageData.global.ts index 47f2698..792c9af 100644 --- a/layers/middleware/pageData.global.ts +++ b/layers/middleware/pageData.global.ts @@ -9,8 +9,8 @@ export default defineNuxtRouteMiddleware(async (to, _from) => { // [TODO] 하이드레이션 에러 처리 if (!import.meta.client) return - const config = useRuntimeConfig() - const stoveApiBaseUrl = config.public.stoveApiUrl + const runtimeConfig = useRuntimeConfig() + const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page` const store = usePageDataStore() diff --git a/layers/plugins/i18n-loader.client.ts b/layers/plugins/i18n-loader.client.ts index de5e787..a957923 100644 --- a/layers/plugins/i18n-loader.client.ts +++ b/layers/plugins/i18n-loader.client.ts @@ -2,7 +2,7 @@ * i18n 다국어 로더 플러그인 * S3에서 공통 다국어 파일을 로드하여 i18n 메시지에 주입합니다. */ -export default defineNuxtPlugin(async (nuxtApp) => { +export default defineNuxtPlugin(async nuxtApp => { const $i18n = nuxtApp.$i18n as any const runtimeConfig = useRuntimeConfig() @@ -31,7 +31,7 @@ export default defineNuxtPlugin(async (nuxtApp) => { 'zh-cn': 'zh-CN', } - langCodes.forEach((langCode) => { + langCodes.forEach(langCode => { // 로케일 코드 변환 (필요한 경우) const normalizedLangCode = localeMap[langCode] || langCode @@ -47,6 +47,9 @@ export default defineNuxtPlugin(async (nuxtApp) => { } }) } catch (error) { - console.error('[Exception] i18n-loader: Failed to load translations:', error) + console.error( + '[Exception] i18n-loader: Failed to load translations:', + error + ) } -}) \ No newline at end of file +}) diff --git a/layers/server/middleware/gameData.ts b/layers/server/middleware/gameData.ts index 47d2399..781ccd7 100644 --- a/layers/server/middleware/gameData.ts +++ b/layers/server/middleware/gameData.ts @@ -1,9 +1,5 @@ import { LRUCache } from 'lru-cache' -import { - getHeader, - getRequestHost, - defineEventHandler, -} from 'h3' +import { getHeader, getRequestHost, defineEventHandler } from 'h3' import { ssrGetFinalLocale } from '../../utils/localeUtil' import type { GameDataResponse } from '../../types/api/gameData' import type { ResGetInspectionData } from '../../types/InspectionType' @@ -17,7 +13,9 @@ import { isStaticFile } from '#layers/utils/commonUtil' * @param customMaxAge - 커스텀 max-age 값 (초 단위) */ function setCacheHeaders( - event: { node: { res: { setHeader: (name: string, value: string) => void } } }, + event: { + node: { res: { setHeader: (name: string, value: string) => void } } + }, cacheMode: 'no-cache' | 'short' | 'medium' | 'default', customMaxAge?: number ): void { @@ -59,7 +57,7 @@ function setCacheHeaders( const cache = new LRUCache({ max: 100, // 캐시에 저장할 최대 항목 수 - ttl: 1000 * 30 // 30초 동안 캐시 유지 + ttl: 1000 * 30, // 30초 동안 캐시 유지 }) /** @@ -69,11 +67,15 @@ const cache = new LRUCache({ * @param finalLocale - 최종 언어 * @param baseDomain - 기본 도메인 */ -function setFinalLocaleCookie(event: any, finalLocale: string, baseDomain: string) { +function setFinalLocaleCookie( + event: any, + finalLocale: string, + baseDomain: string +) { setCookie(event, 'LOCALE', finalLocale.toUpperCase(), { domain: baseDomain, path: '/', - maxAge: 60 * 60 * 24 * 365 // 1년 (초 단위) + maxAge: 60 * 60 * 24 * 365, // 1년 (초 단위) }) } @@ -121,32 +123,30 @@ function fnLocaleMiddleware(event: any, finalLocale: string) { } export default defineEventHandler(async event => { - - // const runType = `${config.public.runType}` - // console.log("🚀 ~ baseDomain:", config.public.baseDomain) + // const runType = `${runtimeConfig.public.runType}` // 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) + // 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) - // } + // ------------------------------------------------------------------------------- + // [Locale Middleware] + // ------------------------------------------------------------------------------- + // fnLocaleMiddleware(event, finalLocale) + // } catch (e) { + // console.error('[Exception] /server/middleware/middleware-02-global: ', e) + // } // } - const config = useRuntimeConfig() - const iBaseApiUrl = `${config.public.stoveApiUrlServer}` - const baseDomain = `${config.public.baseDomain}` - const stoveApiUrlBaseServer = config.public.stoveApiUrlServer + 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 initGameData: GameDataResponse | null = null @@ -165,7 +165,6 @@ export default defineEventHandler(async event => { } try { - const queryParams: Record = { game_domain: cleanHost || '', lang_code: '', @@ -177,13 +176,13 @@ export default defineEventHandler(async event => { // console.log("🚀 ~ 00000 initGameData:", initGameData) initLangCodes = initResponse?.value?.lang_codes || null initDefaultLocale = initResponse?.value?.default_lang_code || null - console.log("🚀 ~ 000111 initLangCodes:", initLangCodes) + console.log('🚀 ~ 000111 initLangCodes:', initLangCodes) } catch (error) { console.error('init gameData load error:', error) } const fullPath = event.path - + // 1-1. 정적 파일 패스 if (isStaticFile(event.path)) { return @@ -192,7 +191,12 @@ export default defineEventHandler(async event => { // 1-2. /inspection 패스 if (fullPath.includes('/inspection')) { // 리턴 되기 전 언어 쿠키 세팅 - finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale) + finalLocale = ssrGetFinalLocale( + event?.node.req.url, + event.node.req.headers, + initLangCodes, + initDefaultLocale + ) setFinalLocaleCookie(event, finalLocale, baseDomain) return } @@ -213,14 +217,17 @@ export default defineEventHandler(async event => { const cacheKey = 'inspection' // console.log("🚀 11111 ~ cacheKey:", cacheKey) - // gameData를 직접 가져와서 context에 저장 (API 호출 없이) try { - - // 2. 언어 코드 추출 - finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale) - console.log("🚀 222 finalLocale:", finalLocale) - + // 2. 언어 코드 추출 + finalLocale = ssrGetFinalLocale( + event?.node.req.url, + event.node.req.headers, + initLangCodes, + initDefaultLocale + ) + console.log('🚀 222 finalLocale:', finalLocale) + const queryParams: Record = { game_domain: cleanHost || '', lang_code: finalLocale, @@ -230,18 +237,16 @@ export default defineEventHandler(async event => { })) as GameDataResponse | null // 언어패스 쿠키 굽기 - 장기방안에서는 굽지않음 - if(initLangCodes?.includes(finalLocale)) { + if (initLangCodes?.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) - + // 점검 데이터 조회 let inspectionData if (cache.has(cacheKey)) { @@ -251,104 +256,109 @@ export default defineEventHandler(async event => { if (response?.value?.game_id) { const inspectionApiUrl = `${iBaseApiUrl}/pub-comm/v3.0/inspection/${response?.value?.game_id}` // 직접 $fetch 사용 (composable 사용하지 않음) - const inspectionResponse = await $fetch(inspectionApiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' + const inspectionResponse = await $fetch( + inspectionApiUrl, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, } - }) + ) inspectionData = inspectionResponse?.value?.inspection cache.set(cacheKey, inspectionData) // 캐시에 저장 // console.log("🚀 ~ inspectionData:", inspectionData) - } } + // 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 + ) - // 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이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ + * - 점검 URL 경로가 아닐 경우 no-cache 설정 + * - 화이트 리스트 체크 + */ + // 현재 경로가 점검 페이지가 아닐 경우 리다이렉트 + const inspectionPath = `/${finalLocale}/inspection` + if (fullPath !== inspectionPath) { + setCacheHeaders(event, 'no-cache') + } - // 5. 점검 상태별 캐시 설정 - if (inspectionData?.inspection_status === 1 && currentTime >= tsStartDate && currentTime <= tsEndDate) { - /** - * 점검 중인 경우 - * - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인ㄹ - * - 점검 URL 경로가 아닐 경우 no-cache 설정 - * - 화이트 리스트 체크 - */ - // 현재 경로가 점검 페이지가 아닐 경우 리다이렉트 - 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_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 { + // 허용된 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 { - /** - * 점검이 아닌 경우 - * - 홈 경로는 no-cache - * - 점검 예정 시간에 따른 캐시 설정 - * - 점검 5분 전: 짧은 캐시 (10초) - * - 점검 30분 전: 중간 캐시 (15초) - * - 점검 30분 이후: 기본 캐시 (60초) - */ - // 홈 경로: 캐시 없음 - const isHomePath = [ - '', - '/' - //, ...Object.values(DEFAULT_LOCALE_COVERAGES).flatMap((locale) => [`/${locale}`, `/${locale}/`]) - ].includes(fullPath) + event.node.res.statusCode = 302 + event.node.res.setHeader('Location', inspectionPath) + event.node.res.end() + } + } 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') - } + 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) } - + // ------------------------------------------------------------------------------- + // [Locale Middleware] + // ------------------------------------------------------------------------------- + fnLocaleMiddleware(event, finalLocale) + } } - } catch (error) { console.error('gameData load error:', error) } diff --git a/layers/stores/useGameDataStore.ts b/layers/stores/useGameDataStore.ts index f22bbfe..8ad26a2 100644 --- a/layers/stores/useGameDataStore.ts +++ b/layers/stores/useGameDataStore.ts @@ -8,10 +8,6 @@ export const useGameDataStore = defineStore('gameData', () => { gameData.value = data } - const setLangCode = (data: string) => { - langCode.value = data - } - const clearGameData = () => { gameData.value = null } @@ -20,7 +16,6 @@ export const useGameDataStore = defineStore('gameData', () => { langCode, gameData, setGameData, - setLangCode, clearGameData, } }) diff --git a/layers/templates/FxSecure01/index.vue b/layers/templates/FxSecure01/index.vue index 45a312d..5dd640e 100644 --- a/layers/templates/FxSecure01/index.vue +++ b/layers/templates/FxSecure01/index.vue @@ -13,10 +13,10 @@ const props = defineProps() const { handleTokenValidation } = useTokenValidation() // Configuration -const config = useRuntimeConfig() -const dataResourcesUrl = config.public.dataResourcesUrl as string +const runtimeConfig = useRuntimeConfig() +const dataResourcesUrl = runtimeConfig.public.dataResourcesUrl as string +const stoveApiBaseUrl = runtimeConfig.public.stoveApiUrl const multilingualFileName = 'STOVE_PUBTEMPLATE_homepage_brand_secure.json' -const stoveApiBaseUrl = config.public.stoveApiUrl // Multilingual const resultGetMultilingual = await useGetMultilingual({ @@ -33,7 +33,7 @@ const isLogin = ref(false) const secureSetting = ref({ otpLoginYn: 'N', abroadLoginBlockYn: 'N', - pcRegisterYn: 'N' + pcRegisterYn: 'N', }) // 회원 보안 설정 설정 @@ -52,26 +52,28 @@ const checkLoginValidation = async () => { // 회원 보안 설정 조회 const fnGetSecuritySetting = async () => { - const accessToken = useCookie('SUAT') checkLoginValidation() const apiBase = `${stoveApiBaseUrl}/auth-secure/v1.0` const headers = { Authorization: `Bearer ${accessToken.value}`, - 'Content-Type': 'application/json;charset=UTF-8' + 'Content-Type': 'application/json;charset=UTF-8', } try { - const result = await commonFetch('GET', `${apiBase}/security/setting`, { headers }) + const result = await commonFetch('GET', `${apiBase}/security/setting`, { + headers, + }) if (result?.code === 0 && Array.isArray(result.value)) { const arrSecure = result.value - const getValue = (key: string) => arrSecure.find((f: any) => f.key === key)?.value ?? 'N' + const getValue = (key: string) => + arrSecure.find((f: any) => f.key === key)?.value ?? 'N' secureSetting.value = { otpLoginYn: getValue('OTP_LOGIN_YN'), abroadLoginBlockYn: getValue('ABROAD_LOGIN_BLOCK_YN'), - pcRegisterYn: getValue('PC_REGISTER_YN') + pcRegisterYn: getValue('PC_REGISTER_YN'), } } } catch (e) { @@ -89,7 +91,9 @@ const secureCards = computed(() => { { id: 'SECURE_CARD_0', title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)', - description: tm('Secure_Stove_otp_desc') || '스토브 앱으로 인증 후 안전하게 로그인하세요.', + description: + tm('Secure_Stove_otp_desc') || + '스토브 앱으로 인증 후 안전하게 로그인하세요.', status: secureSetting.value.otpLoginYn, benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택', benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10', @@ -100,7 +104,9 @@ const secureCards = computed(() => { { id: 'SECURE_CARD_1', title: tm('Secure_Block_foreign_login') || '해외 로그인 차단', - description: tm('Secure_Block_foreign_login_desc') || '접속 국가를 제한하여 의심 로그인을 차단해요.', + description: + tm('Secure_Block_foreign_login_desc') || + '접속 국가를 제한하여 의심 로그인을 차단해요.', status: secureSetting.value.abroadLoginBlockYn, benefitTitle: '', benefitDesc: '', @@ -111,7 +117,9 @@ const secureCards = computed(() => { { id: 'SECURE_CARD_2', title: tm('Secure_Trusted_pc_management') || '지정 PC 관리', - description: tm('Secure_Trusted_pc_desc') || '지정 PC에서만 로그인할 수 있게 설정해 보세요.', + description: + tm('Secure_Trusted_pc_desc') || + '지정 PC에서만 로그인할 수 있게 설정해 보세요.', status: secureSetting.value.pcRegisterYn, benefitTitle: '', benefitDesc: '', @@ -126,7 +134,6 @@ const cautionText = computed(() => { return tm('Secure_Notice_Content') || [] }) - onMounted(() => { fnGetSecuritySetting() }) @@ -143,12 +150,21 @@ onMounted(() => {
-
-

+
+

{{ tm('Secure_Section_Title') || '보안 서비스' }}

-

- {{ tm('Secure_Section_Description') || '*OTP / 해외 로그인 차단 / 지정 PC 관리 설정하고, 로드나인 계정을 보다 안전하게 보호하세요.' }} +

+ {{ + tm('Secure_Section_Description') || + '*OTP / 해외 로그인 차단 / 지정 PC 관리 설정하고, 로드나인 계정을 보다 안전하게 보호하세요.' + }}

@@ -160,7 +176,9 @@ onMounted(() => { class="flex-1 min-h-[308px] md:min-h-[384px] p-[10px] md:p-4 bg-[#FFFFFF] rounded-2xl flex flex-col gap-3 transition-all duration-300 ease-in-out" > -
+
{ : 'bg-[#EBEBEB] text-[#999999]', ]" > - {{ card.status === 'Y' ? tm('Secure_Enabled') : tm('Secure_Disabled') }} + {{ + card.status === 'Y' + ? tm('Secure_Enabled') + : tm('Secure_Disabled') + }}
-

+

{{ card.title }}

-

+

{{ card.description }}

@@ -203,7 +229,9 @@ onMounted(() => { class="w-[48px] h-[48px] bg-[#3C75FF] rounded-[8px] flex items-center justify-center" > { />
-
+
{{ card.benefitTitle }}
{ button-size="size-small md:size-large" background-color="#000000" text-color="#FFFFFF" - @click="isLogin ? handleSecureSetting(card.url) : checkLoginValidation()" + @click=" + isLogin + ? handleSecureSetting(card.url) + : checkLoginValidation() + " > {{ tm('Secure_Action_setup') }} @@ -243,8 +277,19 @@ onMounted(() => { disabled > {{ tm('Secure_Action_complete') }} - - + +
@@ -252,8 +297,12 @@ onMounted(() => {
-
-
+
+
{{ tm('Secure_Notice') }}
    @@ -262,11 +311,10 @@ onMounted(() => { :key="caution" v-dompurify-html="caution" class="relative pl-[22px] before:content-[''] before:absolute before:top-[10px] before:left-[9px] before:w-[3px] before:h-[3px] before:rounded-full before:bg-[#999999] text-[#999999] text-[14px] font-[400] leading-[24px] tracking-[-0.42px]" - > - + >

- \ No newline at end of file + diff --git a/layers/utils/commonUtil.ts b/layers/utils/commonUtil.ts index fb42881..2e47074 100644 --- a/layers/utils/commonUtil.ts +++ b/layers/utils/commonUtil.ts @@ -44,7 +44,9 @@ const csrDownloadFile = (fileUrl: string, fileName?: string) => { */ const csrGetMarketingCode = () => { const route = useRoute() - const mcode = Number(`${route.query.mcode != null && route.query.mcode !== '' ? route.query.mcode : ''}`) + const mcode = Number( + `${route.query.mcode != null && route.query.mcode !== '' ? route.query.mcode : ''}` + ) return isNaN(mcode) ? undefined : mcode } @@ -82,15 +84,20 @@ const isNumeric = (str: string): boolean => { * @param {Function} tm - i18n의 tm 함수 (예: (key) => ({ txt: string })) * @param {any} query - 추가 쿼리 파라미터 */ -const getParsedCustomLink = (link: string, { tm, query = {} }: ParsedCustomLinkOptions) => { - const config = useRuntimeConfig() +const getParsedCustomLink = ( + link: string, + { tm, query = {} }: ParsedCustomLinkOptions +) => { + const runtimeConfig = useRuntimeConfig() let result = `${link || ''}` // @c{key} 패턴 치환 (예: @c{stoveCommunityUrl}) if (link.includes('@c')) { result = result.replace(/@c\{(.*?)\}/g, (_, key) => { - // config.public에서 해당 key 값을 찾아 치환 - return typeof config.public[key] === 'string' ? config.public[key] : '' + // runtimeConfig.public에서 해당 key 값을 찾아 치환 + return typeof runtimeConfig.public[key] === 'string' + ? runtimeConfig.public[key] + : '' }) } @@ -132,7 +139,7 @@ const setCookieForDay = (name: string, value: string, exp?: number) => { const setCookie = useCookie(name, { expires: new Date(date), - path: '/' + path: '/', }) setCookie.value = value @@ -140,7 +147,9 @@ const setCookieForDay = (name: string, value: string, exp?: number) => { // 정적 파일인지 확인하는 함수 const isStaticFile = (path: string): boolean => { - return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|scss)$/i.test(path) + return /\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|scss)$/i.test( + path + ) } /** @@ -164,5 +173,5 @@ export { getParsedCustomLink, setCookieForDay, isStaticFile, - isInRange + isInRange, } diff --git a/layers/utils/localeUtil.ts b/layers/utils/localeUtil.ts index 33117ff..2e8b6d2 100644 --- a/layers/utils/localeUtil.ts +++ b/layers/utils/localeUtil.ts @@ -4,7 +4,7 @@ import { DEFAULT_LOCALE_CODE } from '../../i18n.config' export const getPreferredLanguage = (acceptLanguageHeader = '') => { const languages = acceptLanguageHeader .split(',') - .map((lang) => { + .map(lang => { const [code, priority = 'q=1'] = lang.trim().split(';q=') return { code, priority: parseFloat(priority) } }) @@ -17,7 +17,7 @@ export const getPreferredLanguage = (acceptLanguageHeader = '') => { const parseCookies = (cookieHeader: string) => { const cookies: Record = {} if (cookieHeader) { - cookieHeader.split(';').forEach((cookie) => { + cookieHeader.split(';').forEach(cookie => { const [name, value] = cookie.trim().split('=') if (name && value) { cookies[name] = decodeURIComponent(value) @@ -33,13 +33,17 @@ const parseCookies = (cookieHeader: string) => { * @param {string} path - 현재 URL 경로 */ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => { - const config = useRuntimeConfig() - const baseDomain = `${config.public.baseDomain}` + const runtimeConfig = useRuntimeConfig() + const baseDomain = `${runtimeConfig.public.baseDomain}` let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정 // coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환 - if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) { + if ( + !coveragesLocales || + !Array.isArray(coveragesLocales) || + coveragesLocales.length === 0 + ) { return finalLocale } @@ -60,15 +64,21 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => { } // 2. LOCALE 쿠키 언어 - const cookieLanguage = `${useCookie('LOCALE', { domain: baseDomain }).value}`.toLowerCase() + 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)) { + const browserLanguage = + `${navigator.language || navigator.languages[0]}`.toLowerCase() + if ( + browserLanguage && + browserLanguage !== '' && + coveragesLocales.includes(browserLanguage) + ) { finalLocale = browserLanguage return finalLocale } @@ -85,11 +95,20 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => { * @param {string} path - 현재 URL 경로 * @param {any} headers - 요청 헤더 */ -export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: string[], defaultLocale: string) => { +export const ssrGetFinalLocale = ( + path = '', + headers: any, + coveragesLocales: string[], + defaultLocale: string +) => { let finalLocale = defaultLocale || DEFAULT_LOCALE_CODE // 기본값 설정 try { // coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환 - if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) { + if ( + !coveragesLocales || + !Array.isArray(coveragesLocales) || + coveragesLocales.length === 0 + ) { return finalLocale } @@ -101,7 +120,11 @@ export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: str } const pathLocalee = `${path.split('/')[1]}`.toLowerCase() // URL path에 포함된 언어 정보가 지원하는 언어인지 체크 - if (pathLocalee && pathLocalee !== '' && coveragesLocales.includes(pathLocalee)) { + if ( + pathLocalee && + pathLocalee !== '' && + coveragesLocales.includes(pathLocalee) + ) { finalLocale = pathLocalee return finalLocale } @@ -110,8 +133,14 @@ export const ssrGetFinalLocale = (path = '', headers: any, coveragesLocales: str // 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)) { + const cookieLanguage = cookies.LOCALE + ? `${cookies.LOCALE}`.toLowerCase() + : '' + if ( + cookieLanguage && + cookieLanguage !== '' && + coveragesLocales.includes(cookieLanguage) + ) { finalLocale = cookieLanguage return finalLocale } diff --git a/layers/utils/stoveUtil.ts b/layers/utils/stoveUtil.ts index 8126e79..ee6a019 100644 --- a/layers/utils/stoveUtil.ts +++ b/layers/utils/stoveUtil.ts @@ -9,10 +9,10 @@ import { csrFormatJWT } from '#layers/utils/formatUtil' * Stove 로그인 */ export const csrGoStoveLogin = () => { - const config = useRuntimeConfig() + const runtimeConfig = useRuntimeConfig() const gameDataStore = useGameDataStore() - const loginUrl = config.public.stoveLoginUrl + const loginUrl = runtimeConfig.public.stoveLoginUrl const stoveGameId = gameDataStore.gameData?.game_id const stoveGameNo = gameDataStore.gameData?.game_code const redirectUrl = encodeURIComponent(location.href) diff --git a/layers/utils/styleUtil.ts b/layers/utils/styleUtil.ts index cd45095..a750768 100644 --- a/layers/utils/styleUtil.ts +++ b/layers/utils/styleUtil.ts @@ -23,8 +23,8 @@ export const getImageHost = ( if (/^(https?:\/\/|www\.)/.test(path)) return path - const config = useRuntimeConfig() - const { staticUrl, assetsUrl } = config.public + const runtimeConfig = useRuntimeConfig() + const { staticUrl, assetsUrl } = runtimeConfig.public const { imageType = 'game' } = options const isDevelopment = import.meta.dev