refactor: 스톱GNB theme 수정, 언어 정책 수정, 보안강화페이지 API 적용
This commit is contained in:
@@ -8,6 +8,7 @@ onMounted(() => {
|
||||
const langCodes = gameData?.lang_codes
|
||||
const defaultLangCode = gameData?.default_lang_code
|
||||
const stoveGnbData = gameData?.stove_gnb_json
|
||||
const designTheme = gameData?.design_theme
|
||||
|
||||
const currentDomain =
|
||||
window.location.protocol + '//' + window.location.hostname
|
||||
@@ -39,10 +40,11 @@ onMounted(() => {
|
||||
mode: {
|
||||
theme: {
|
||||
default:
|
||||
stoveGnbData?.skin_type === 'gnb-dark-mini' ? 'dark' : 'light',
|
||||
designTheme === 1 ? 'light' : 'dark',
|
||||
support: ['dark', 'light'],
|
||||
},
|
||||
mini: true,
|
||||
layout: 'wide',
|
||||
fixed: false,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
interface ReqApiData {
|
||||
baseApiUrl: string
|
||||
url: string
|
||||
}
|
||||
|
||||
export const useApiData = async (req: ReqApiData): Promise<any> => {
|
||||
const dataUrl = `${req.baseApiUrl}/${req.url}` // 정상 URL 경로
|
||||
try {
|
||||
const fetch = await $fetch<any>(dataUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
})
|
||||
return fetch
|
||||
} catch (error) {
|
||||
console.log('error', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export const useGetGameDataExternal = () => {
|
||||
console.log("🚀 ~ getGameDataExternal ~ req:", req)
|
||||
// const config = useRuntimeConfig()
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiUrl = `${config.public.stoveApiUrl}`
|
||||
const apiUrl = `${stoveApiUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v1.0/template/game?game_domain=${req.gameDomain}&lang_code=${req.langCode}`
|
||||
|
||||
try {
|
||||
const response = (await commonFetch('GET', apiUrl)) as GameDataResponse
|
||||
|
||||
44
layers/composables/useSecure.ts
Normal file
44
layers/composables/useSecure.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* [보안 캠페인] 보안 캠페인 - 회원 보안 설정 정보 조회
|
||||
*/
|
||||
export const getSecuritySetting = async (accessToken: string) => {
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiBase = `${stoveApiBaseUrl}/auth-secure/v1.0`
|
||||
const headers = {
|
||||
'caller-id': String(config.public.stoveCallerId),
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
}
|
||||
|
||||
try {
|
||||
return await commonFetch('GET', `${apiBase}/security/setting`, { headers })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 로그인 모달 표시
|
||||
const showLoginModal = () => {
|
||||
modalStore.handleOpenConfirm({
|
||||
contentText: '로그인이 필요합니다.',
|
||||
confirmButtonText: '스토브 로그인',
|
||||
modalName: 'modal-login',
|
||||
confirmButtonEvent: () => {
|
||||
csrGoStoveLogin()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// 토큰 유효성 체크
|
||||
const { validateToken } = useTokenValidation()
|
||||
const validateTokenResult = await validateToken(accessTokenSub.value || '')
|
||||
|
||||
// 토큰 유효성 체크 실패 시 로그인 모달 표시
|
||||
if (!validateTokenResult) {
|
||||
showLoginModal()
|
||||
isProcessing.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
// const baseDomain = `${config.public.baseDomain}`
|
||||
const stoveApiUrl = `${config.public.stoveApiUrl}`
|
||||
const stoveGameId = `${gameData.value.game_id}`
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const stoveGameId = gameData.value.game_id
|
||||
// const stoveMaintenanceApiUrl = `${config.public.stoveMaintenanceApiUrl}`
|
||||
|
||||
// const localeCookie = useCookie('LOCALE', {
|
||||
@@ -20,7 +20,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
||||
|
||||
// 웹 점검 -----
|
||||
const { isWebInspection, getInspectionDataExternal } = useGetInspectionDataExternal()
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiUrl, gameId: stoveGameId })
|
||||
await getInspectionDataExternal({ baseApiUrl: stoveApiBaseUrl, gameId: stoveGameId })
|
||||
|
||||
|
||||
// 게임 점검 -----
|
||||
|
||||
@@ -3,17 +3,25 @@ import { usePageDataStore } from '#layers/stores/usePageDataStore'
|
||||
import { useGetGameDomain } from '#layers/composables/useGetGameDomain'
|
||||
import { usePathResolver } from '#layers/composables/usePathResolver'
|
||||
import type { PageDataResponse } from '#layers/types/api/pageData'
|
||||
import type {
|
||||
GameDataValue,
|
||||
} from '#layers/types/api/gameData'
|
||||
|
||||
export default defineNuxtRouteMiddleware(async (to, _from) => {
|
||||
if (!import.meta.client) return
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
|
||||
const store = usePageDataStore()
|
||||
const gameDomain = useGetGameDomain()
|
||||
const { getPathAfterLanguage } = usePathResolver()
|
||||
const headers = useRequestHeaders()
|
||||
const langCode = ssrGetFinalLocale(to.path, headers)
|
||||
const gameDataStore = useGameDataStore()
|
||||
const gameData = gameDataStore.gameData as GameDataValue
|
||||
|
||||
const stoveApiBaseUrl = config.public.stoveApiUrl
|
||||
const apiUrl = `${stoveApiBaseUrl}/pub-comm/v2.0/template/page`
|
||||
const langCode = ssrGetFinalLocale(to.path, headers, gameData?.lang_codes, gameData?.default_lang_code)
|
||||
|
||||
try {
|
||||
if(to.path.includes('inspection')) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
getHeader,
|
||||
getRequestHost,
|
||||
defineEventHandler,
|
||||
getRequestURL,
|
||||
} from 'h3'
|
||||
import { ssrGetFinalLocale } from '../../utils/localeUtil'
|
||||
import type { GameDataResponse } from '../../types/api/gameData'
|
||||
@@ -123,10 +122,7 @@ function fnLocaleMiddleware(event: any, finalLocale: string) {
|
||||
|
||||
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)
|
||||
|
||||
@@ -147,6 +143,45 @@ export default defineEventHandler(async event => {
|
||||
// }
|
||||
// }
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const iBaseApiUrl = `${config.public.stoveApiUrlServer}`
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
const stoveApiUrlBaseServer = config.public.stoveApiUrlServer
|
||||
const apiUrl = `${stoveApiUrlBaseServer}/pub-comm/v1.0/template/game`
|
||||
|
||||
const initGameData: GameDataResponse | null = null
|
||||
let initLangCodes: string[] | null = null
|
||||
let finalLocale
|
||||
let cleanHost
|
||||
let initDefaultLocale
|
||||
|
||||
const host =
|
||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
||||
const isGameDomainExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameDomainExtractable) {
|
||||
cleanHost = host.split(':')[0]
|
||||
event.context.gameDomain = cleanHost
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
game_domain: cleanHost || '',
|
||||
lang_code: '',
|
||||
}
|
||||
const initResponse = (await $fetch(apiUrl, {
|
||||
query: queryParams,
|
||||
})) as GameDataResponse | null
|
||||
// initGameData = initResponse || null
|
||||
// console.log("🚀 ~ 00000 initGameData:", initGameData)
|
||||
initLangCodes = initResponse?.value?.lang_codes || null
|
||||
initDefaultLocale = initResponse?.value?.default_lang_code || null
|
||||
console.log("🚀 ~ 000111 initLangCodes:", initLangCodes)
|
||||
} catch (error) {
|
||||
console.error('init gameData load error:', error)
|
||||
}
|
||||
|
||||
const fullPath = event.path
|
||||
|
||||
// 1-1. 정적 파일 패스
|
||||
@@ -157,8 +192,8 @@ export default defineEventHandler(async event => {
|
||||
// 1-2. /inspection 패스
|
||||
if (fullPath.includes('/inspection')) {
|
||||
// 리턴 되기 전 언어 쿠키 세팅
|
||||
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
// setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -178,30 +213,16 @@ export default defineEventHandler(async event => {
|
||||
const cacheKey = 'inspection'
|
||||
// console.log("🚀 11111 ~ cacheKey:", cacheKey)
|
||||
|
||||
const host =
|
||||
(getHeader(event, 'host') || getRequestHost(event)).toString() || ''
|
||||
const isGameDomainExtractable = host.includes(baseDomain)
|
||||
|
||||
if (isGameDomainExtractable) {
|
||||
const cleanHost = host.split(':')[0]
|
||||
event.context.gameDomain = cleanHost
|
||||
}
|
||||
|
||||
// gameData를 직접 가져와서 context에 저장 (API 호출 없이)
|
||||
try {
|
||||
const config = useRuntimeConfig()
|
||||
const stoveApiUrlServer = config.public.stoveApiUrlServer
|
||||
const apiUrl = `${stoveApiUrlServer}/pub-comm/v1.0/template/game`
|
||||
|
||||
// console.log("🚀 ~ apiUrl:", apiUrl)
|
||||
|
||||
// 2. 언어 코드 추출
|
||||
// const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers, initLangCodes, initDefaultLocale)
|
||||
console.log("🚀 222 finalLocale:", finalLocale)
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
game_domain: event.context.gameDomain || '',
|
||||
game_domain: cleanHost || '',
|
||||
lang_code: finalLocale,
|
||||
}
|
||||
const response = (await $fetch(apiUrl, {
|
||||
@@ -209,10 +230,9 @@ export default defineEventHandler(async event => {
|
||||
})) as GameDataResponse | null
|
||||
|
||||
// 언어패스 쿠키 굽기 - 장기방안에서는 굽지않음
|
||||
// const langCoverages = response?.value?.lang_codes || []
|
||||
// if(langCoverages.includes(finalLocale)) {
|
||||
// setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
// }
|
||||
if(initLangCodes?.includes(finalLocale)) {
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ interface Props {
|
||||
pageVerTmplSeq: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// Configuration
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
@@ -25,36 +26,73 @@ const { tm }: any = useI18n({
|
||||
messages: Object(resultGetMultilingual?.value?.multilingual),
|
||||
})
|
||||
|
||||
const secureSetting = ref({
|
||||
otpLoginYn: 'N',
|
||||
abroadLoginBlockYn: 'N',
|
||||
pcRegisterYn: 'N'
|
||||
})
|
||||
|
||||
// Functions
|
||||
const handleSecureSetting = (_cardId: string) => {
|
||||
// TODO: 보안 설정 페이지로 이동하거나 설정 로직 구현
|
||||
// 예: window.open('https://stove.kr/security', '_blank')
|
||||
const handleSecureSetting = (url: string) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
||||
// 회원 보안 설정 조회
|
||||
const fnGetSecuritySetting = async () => {
|
||||
try {
|
||||
const accessToken = csrGetAccessToken() ?? ''
|
||||
const result = (await getSecuritySetting(accessToken)) as { code: number; message: string; value: any[] }
|
||||
console.log("🚀 ~ fnGetSecuritySetting ~ result:", result)
|
||||
|
||||
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'
|
||||
|
||||
secureSetting.value = {
|
||||
otpLoginYn: getValue('OTP_LOGIN_YN'),
|
||||
abroadLoginBlockYn: getValue('ABROAD_LOGIN_BLOCK_YN'),
|
||||
pcRegisterYn: getValue('PC_REGISTER_YN')
|
||||
}
|
||||
} else {
|
||||
secureSetting.value = {
|
||||
otpLoginYn: 'N',
|
||||
abroadLoginBlockYn: 'N',
|
||||
pcRegisterYn: 'N'
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
secureSetting.value = {
|
||||
otpLoginYn: 'N',
|
||||
abroadLoginBlockYn: 'N',
|
||||
pcRegisterYn: 'N'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data
|
||||
const backgroundData = computed(() =>
|
||||
getComponentGroup(props.components, 'background')
|
||||
)
|
||||
console.log("🚀 ~ backgroundData:", backgroundData)
|
||||
const secureCardsData = computed(() =>
|
||||
getComponentGroupAry(props.components, 'secureCard')
|
||||
)
|
||||
// const secureCardsData = computed(() =>
|
||||
// getComponentGroup(props.components, 'secureCard')
|
||||
// )
|
||||
|
||||
// Computed
|
||||
const secureCards = computed(() => {
|
||||
if (secureCardsData.value && secureCardsData.value.length > 0) {
|
||||
return secureCardsData.value.map((card, index) => ({
|
||||
id: `SECURE_CARD_${index}`,
|
||||
title: card?.display?.text || '',
|
||||
description: card?.display?.description || '',
|
||||
status: card?.display?.status || 'off', // 'on' | 'off'
|
||||
benefitTitle: card?.display?.benefitTitle || '',
|
||||
benefitDesc: card?.display?.benefitDesc || '',
|
||||
benefitIcon: card?.display?.benefitIcon || '',
|
||||
buttonText: card?.display?.buttonText || '설정하기',
|
||||
buttonDisabled: card?.display?.buttonDisabled || false,
|
||||
}))
|
||||
}
|
||||
// if (secureCardsData.value && secureCardsData.value.length > 0) {
|
||||
// return secureCardsData.value.map((card, index) => ({
|
||||
// id: `SECURE_CARD_${index}`,
|
||||
// title: card?.display?.text || '',
|
||||
// description: card?.display?.description || '',
|
||||
// status: card?.display?.status || 'off', // 'on' | 'off'
|
||||
// benefitTitle: card?.display?.benefitTitle || '',
|
||||
// benefitDesc: card?.display?.benefitDesc || '',
|
||||
// benefitIcon: card?.display?.benefitIcon || '',
|
||||
// buttonText: card?.display?.buttonText || '설정하기',
|
||||
// buttonDisabled: card?.display?.buttonDisabled || false,
|
||||
// }))
|
||||
// }
|
||||
// 기본 데이터 (Figma 디자인 기반)
|
||||
|
||||
|
||||
@@ -63,92 +101,118 @@ const secureCards = computed(() => {
|
||||
id: 'SECURE_CARD_0',
|
||||
title: tm('Secure_Stove_otp') || '스토브 인증기 (OTP)',
|
||||
description: tm('Secure_Stove_otp_desc') || '스토브 앱으로 인증 후 안전하게 로그인하세요.',
|
||||
status: 'off',
|
||||
status: secureSetting.value.otpLoginYn,
|
||||
benefitTitle: tm('Secure_Stove_otp_benefits') || '스토브 OTP 혜택',
|
||||
benefitDesc: tm('Secure_Defense_bonus_10') || '방어력 +10',
|
||||
benefitIcon: '/images/common/img_OTP.png',
|
||||
buttonText: tm('Secure_Action_setup') || '설정하기',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_OtpLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_1',
|
||||
title: tm('Secure_Block_foreign_login') || '해외 로그인 차단',
|
||||
description: tm('Secure_Block_foreign_login_desc') || '접속 국가를 제한하여 의심 로그인을 차단해요.',
|
||||
status: 'on',
|
||||
status: secureSetting.value.abroadLoginBlockYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonText: tm('Secure_Action_complete') || '설정완료',
|
||||
buttonDisabled: true,
|
||||
url: tm('Secure_AbroadLogin_Url'),
|
||||
},
|
||||
{
|
||||
id: 'SECURE_CARD_2',
|
||||
title: tm('Secure_Trusted_pc_management') || '지정 PC 관리',
|
||||
description: tm('Secure_Trusted_pc_desc') || '지정 PC에서만 로그인할 수 있게 설정해 보세요.',
|
||||
status: 'off',
|
||||
status: secureSetting.value.pcRegisterYn,
|
||||
benefitTitle: '',
|
||||
benefitDesc: '',
|
||||
benefitIcon: '',
|
||||
buttonText: tm('Secure_Action_setup') || '설정하기',
|
||||
buttonDisabled: false,
|
||||
url: tm('Secure_PcRegister_Url'),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
console.log("🚀 ~ secureCards:", secureCards)
|
||||
// 유의사항 내용 다국어 조회
|
||||
const cautionText = computed(() => {
|
||||
return tm('Secure_Notice_Content') || []
|
||||
})
|
||||
const isLogin = () => {
|
||||
const accessToken = csrGetAccessToken() ?? ''
|
||||
return Boolean(accessToken)
|
||||
}
|
||||
|
||||
// 로그인 모달 표시
|
||||
const showLoginModal = () => {
|
||||
modalStore.handleOpenConfirm({
|
||||
contentText: '로그인이 필요합니다.',
|
||||
confirmButtonText: '스토브 로그인',
|
||||
modalName: 'modal-login',
|
||||
|
||||
confirmButtonEvent: () => {
|
||||
csrGoStoveLogin()
|
||||
},
|
||||
})
|
||||
}
|
||||
onMounted(async () => {
|
||||
if (!isLogin()) {
|
||||
showLoginModal()
|
||||
} else {
|
||||
await fnGetSecuritySetting()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WidgetsFixMainTitle
|
||||
:title="tm('Secure_Page_Title') || '보안 강화 캠페인'"
|
||||
:resources-data="backgroundData"
|
||||
class="mx-auto"
|
||||
/>
|
||||
|
||||
<div class="section-container static">
|
||||
<section class="section-secure bg-[#F0F0F0] px-10 pb-50">
|
||||
<div class="content-standard max-w-[1300px] mx-auto">
|
||||
<section class="section-secure bg-[#F0F0F0] pb-50">
|
||||
<div class="section-static content-standa md:max-w-[1300px] mx-auto">
|
||||
<!-- Title Section -->
|
||||
<div class="flex flex-col md:flex-row w-full md:items-end justify-between gap-5 mb-6">
|
||||
<h3 class="text-[#1F1F1F] text-2xl font-bold leading-8 tracking-[-0.72px]">
|
||||
{{ tm('Secure_Section_Title') || '보안 서비스' }}
|
||||
</h3>
|
||||
<p class="text-[#666666] text-[14px] font-[400] leading-[24px] tracking-[-0.42px] text-right">
|
||||
<p class="text-gray-500 text-[14px] font-[400] leading-[24px] tracking-[-0.42px] text-left md:text-right">
|
||||
{{ tm('Secure_Section_Description') || '*OTP / 해외 로그인 차단 / 지정 PC 관리 설정하고, 로드나인 계정을 보다 안전하게 보호하세요.' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Secure Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 w-full gap-[20px] mb-[24px]">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-3 w-full gap-3 md:gap-5 mb-6">
|
||||
<div
|
||||
v-for="card in secureCards"
|
||||
:key="card.id"
|
||||
class="flex-1 min-h-[384px] p-[16px] bg-[#FFFFFF] rounded-2xl flex flex-col gap-[12px]"
|
||||
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"
|
||||
>
|
||||
<!-- Card Content -->
|
||||
<div class="flex-1 p-8 flex flex-col gap-[12px] text-left">
|
||||
<div class="flex-1 p-[10px] md:p-8 flex flex-col gap-[8px] md:gap-3 text-left">
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex">
|
||||
<span
|
||||
:class="[
|
||||
'px-2 py-1 rounded-full text-[14px] font-medium leading-5',
|
||||
card.status === 'on'
|
||||
'px-1.5 md:px-2 py-0.5 md:py-1 rounded-full text-[12px] md:text-[14px] font-medium leading-5',
|
||||
card.status === 'Y'
|
||||
? 'bg-[#E2EAFF] text-[#3C75FF]'
|
||||
: 'bg-[#EBEBEB] text-[#999999]',
|
||||
]"
|
||||
>
|
||||
{{ card.status === 'on' ? tm('Secure_Enabled') : tm('Secure_Disabled') }}
|
||||
{{ card.status === 'Y' ? tm('Secure_Enabled') : tm('Secure_Disabled') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<h4 class="text-[#1F1F1F] text-[24px] font-bold leading-[34px] tracking-[-0.72px]">
|
||||
<h4 class="text-[#1F1F1F] text-[18px] md:text-[24px] font-bold leading-[26px] md:leading-[34px] tracking-[-0.54px] md:tracking-[-0.72px]">
|
||||
{{ card.title }}
|
||||
</h4>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="flex-1 text-[#999999] text-[16px] font-[400] leading-[26px] tracking-[-0.48px]">
|
||||
<p class="flex-1 text-[#999999] text-[14px] md:text-base font-[400] leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.48px]">
|
||||
{{ card.description }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -156,7 +220,7 @@ const cautionText = computed(() => {
|
||||
<!-- Benefit Section -->
|
||||
<div
|
||||
:class="[
|
||||
'self-stretch p-4 rounded-2xl flex flex-col gap-4',
|
||||
'self-stretch p-[10px] md:p-4 rounded-2xl flex flex-col gap-4',
|
||||
card.benefitTitle ? 'bg-[#F0F4FF]' : '',
|
||||
]"
|
||||
>
|
||||
@@ -178,12 +242,12 @@ const cautionText = computed(() => {
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1 flex flex-col text-left">
|
||||
<div class="text-[#3C75FF] text-[18px] font-bold leading-[26px] tracking-[-0.54px]">
|
||||
<div class="text-[#3C75FF] text-[14px] md:text-[18px] font-bold leading-[22px] md:leading-[26px] tracking-[-0.42px] md:tracking-[-0.54px]">
|
||||
{{ card.benefitTitle }}
|
||||
</div>
|
||||
<div
|
||||
v-if="card.benefitDesc"
|
||||
class="text-[#3C75FF] text-[13px] font-[400] leading-[22px] tracking-[-0.325px] opacity-90"
|
||||
class="text-[#3C75FF] text-[12px] md:text-[13px] font-[400] leading-[18px] md:leading-[22px] tracking-[-0.325px] opacity-90"
|
||||
>
|
||||
{{ card.benefitDesc }}
|
||||
</div>
|
||||
@@ -192,25 +256,27 @@ const cautionText = computed(() => {
|
||||
|
||||
<!-- Button -->
|
||||
<AtomsButton
|
||||
type="action"
|
||||
button-size="size-small"
|
||||
:background-color="card.buttonDisabled ? '#EBEBEB' : '#383838'"
|
||||
:text-color="card.buttonDisabled ? '#999999' : '#FFFFFF'"
|
||||
:disabled="card.buttonDisabled"
|
||||
class="w-full h-14 px-10 rounded-2 border border-solid border-black/10 backdrop-blur-10"
|
||||
@click="handleSecureSetting(card.id)"
|
||||
v-if="card.status === 'N'"
|
||||
type="external"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#000000"
|
||||
text-color="#FFFFFF"
|
||||
@click="isLogin() ? handleSecureSetting(card.url): showLoginModal()"
|
||||
>
|
||||
<span>{{ card.buttonText }}</span>
|
||||
<AtomsIconsLongArrowRightLine
|
||||
v-if="!card.buttonDisabled"
|
||||
:size="20"
|
||||
color="#FFFFFF"
|
||||
class="ml-[4px]"
|
||||
/>
|
||||
<svg v-else width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.4298 2.80644L6.84645 0.240655C7.52385 -0.0802185 8.30948 -0.0802184 8.98688 0.240655L14.4035 2.80645C15.2767 3.22003 15.8333 4.09952 15.8333 5.06564V7.65038C15.8333 13.399 10.6191 16.1288 8.65401 16.9535C8.18024 17.1523 7.6531 17.1523 7.17932 16.9535C5.21423 16.1288 -0.000131724 13.399 2.49573e-09 7.65038L1.11287e-05 5.06566C6.95637e-06 4.09953 0.556675 3.22002 1.4298 2.80644ZM11.4226 7.4063C11.748 7.08086 11.748 6.55323 11.4226 6.22779C11.0972 5.90235 10.5695 5.90235 10.2441 6.22779L7.5 8.97187L6.00592 7.47779C5.68049 7.15235 5.15285 7.15235 4.82741 7.47779C4.50197 7.80323 4.50197 8.33086 4.82741 8.6563L6.91074 10.7396C7.23618 11.0651 7.76382 11.0651 8.08926 10.7396L11.4226 7.4063Z" fill="#999999"/>
|
||||
<span>{{ tm('Secure_Action_setup') }}</span>
|
||||
</AtomsButton>
|
||||
<AtomsButton
|
||||
v-else
|
||||
type="action"
|
||||
button-size="size-small md:size-large"
|
||||
background-color="#EBEBEB"
|
||||
text-color="#999999"
|
||||
disabled
|
||||
>
|
||||
<span>{{ tm('Secure_Action_complete') }}</span>
|
||||
<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.4298 2.80644L6.84645 0.240655C7.52385 -0.0802185 8.30948 -0.0802184 8.98688 0.240655L14.4035 2.80645C15.2767 3.22003 15.8333 4.09952 15.8333 5.06564V7.65038C15.8333 13.399 10.6191 16.1288 8.65401 16.9535C8.18024 17.1523 7.6531 17.1523 7.17932 16.9535C5.21423 16.1288 -0.000131724 13.399 2.49573e-09 7.65038L1.11287e-05 5.06566C6.95637e-06 4.09953 0.556675 3.22002 1.4298 2.80644ZM11.4226 7.4063C11.748 7.08086 11.748 6.55323 11.4226 6.22779C11.0972 5.90235 10.5695 5.90235 10.2441 6.22779L7.5 8.97187L6.00592 7.47779C5.68049 7.15235 5.15285 7.15235 4.82741 7.47779C4.50197 7.80323 4.50197 8.33086 4.82741 8.6563L6.91074 10.7396C7.23618 11.0651 7.76382 11.0651 8.08926 10.7396L11.4226 7.4063Z" fill="#999999"/>
|
||||
</svg>
|
||||
|
||||
</AtomsButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,11 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
|
||||
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
|
||||
// coveragesLocales가 빈 배열이거나 유효하지 않은 경우 기본 언어 반환
|
||||
if (!coveragesLocales || !Array.isArray(coveragesLocales) || coveragesLocales.length === 0) {
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 1. URL 패스에 포함된 언어
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
@@ -80,9 +85,14 @@ export const csrGetFinalLocale = (path = '', coveragesLocales: string[]) => {
|
||||
* @param {string} path - 현재 URL 경로
|
||||
* @param {any} headers - 요청 헤더
|
||||
*/
|
||||
export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
let finalLocale = DEFAULT_LOCALE_CODE // 기본값 설정
|
||||
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) {
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
// 1. URL path에 포함된 언어 정보
|
||||
if (path && path !== '' && path.split('/').length > 1) {
|
||||
// 쿼리스트링 제거한 순수 path 검사
|
||||
@@ -91,7 +101,7 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
}
|
||||
const pathLocalee = `${path.split('/')[1]}`.toLowerCase()
|
||||
// URL path에 포함된 언어 정보가 지원하는 언어인지 체크
|
||||
if (pathLocalee && pathLocalee !== '') {
|
||||
if (pathLocalee && pathLocalee !== '' && coveragesLocales.includes(pathLocalee)) {
|
||||
finalLocale = pathLocalee
|
||||
return finalLocale
|
||||
}
|
||||
@@ -101,7 +111,7 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
const cookieHeader = headers.cookie || ''
|
||||
const cookies = parseCookies(cookieHeader)
|
||||
const cookieLanguage = cookies.LOCALE ? `${cookies.LOCALE}`.toLowerCase() : ''
|
||||
if (cookieLanguage && cookieLanguage !== '') {
|
||||
if (cookieLanguage && cookieLanguage !== '' && coveragesLocales.includes(cookieLanguage)) {
|
||||
finalLocale = cookieLanguage
|
||||
return finalLocale
|
||||
}
|
||||
@@ -116,21 +126,21 @@ export const ssrGetFinalLocale = (path = '', headers: any) => {
|
||||
const preferredLocale = getPreferredLanguage(acceptLanguage)
|
||||
if (preferredLocale) {
|
||||
// 선호 언어의 기본 코드와 일치하는 지원 로케일 찾기
|
||||
// const matchedLocale = coveragesLocales.find((locale: string) =>
|
||||
// preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
|
||||
// )
|
||||
// if (matchedLocale) {
|
||||
// finalLocale = matchedLocale.toLowerCase()
|
||||
// return finalLocale
|
||||
// }
|
||||
const matchedLocale = coveragesLocales.find((locale: string) =>
|
||||
preferredLocale.toLowerCase().startsWith(locale.toLowerCase())
|
||||
)
|
||||
if (matchedLocale) {
|
||||
finalLocale = matchedLocale.toLowerCase()
|
||||
return finalLocale
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 서비스 기본 언어
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
} catch (e) {
|
||||
finalLocale = DEFAULT_LOCALE_CODE
|
||||
finalLocale = defaultLocale
|
||||
} catch {
|
||||
finalLocale = defaultLocale
|
||||
}
|
||||
return finalLocale
|
||||
}
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
import { LRUCache } from 'lru-cache'
|
||||
// import { DEFAULT_LOCALE_COVERAGES } from '@/i18n.config'
|
||||
import { getTrueClientIp } from '#layers/utils/apiUtil'
|
||||
import { ssrGetFinalLocale } from '#layers/utils/localeUtil'
|
||||
import type { ResGetInspectionData, WebInspectionData } from '#layers/types/InspectionType'
|
||||
import { isStaticFile } from '#layers/utils/commonUtil'
|
||||
|
||||
|
||||
console.log("🚀 ~ setCacheHeaders ~ event.node.res.setHeader:")
|
||||
/**
|
||||
* 캐시 제어 헤더를 설정하는 공통 함수
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
// 바로 캐시 제어 헤더 적용
|
||||
}
|
||||
|
||||
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 gameId = `${event.context.gameData?.game_id}`
|
||||
const baseDomain = `${config.public.baseDomain}`
|
||||
|
||||
if (['local', 'local-gate8', 'dev'].includes(runType)) {
|
||||
// Sandbox 이상 환경에서만 동작 및 확인 가능 (local, dev는 통과 처리)
|
||||
try {
|
||||
// 언어 코드 추출
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
} catch (e) {
|
||||
console.error('[Exception] /server/middleware/middleware-global: ', e)
|
||||
}
|
||||
} else {
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Inspection Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
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)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
return
|
||||
}
|
||||
|
||||
// 1-3. 특정 경로 패스 (API, 리소스)
|
||||
if (
|
||||
fullPath.startsWith('/api/') ||
|
||||
fullPath.startsWith('/_nuxt/') ||
|
||||
fullPath.includes('/assets/') ||
|
||||
fullPath.includes('favicon')
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
// 캐시 키 생성
|
||||
const cacheKey = 'inspection'
|
||||
|
||||
try {
|
||||
// 2. 언어 코드 추출
|
||||
const finalLocale = ssrGetFinalLocale(event?.node.req.url, event.node.req.headers)
|
||||
setFinalLocaleCookie(event, finalLocale, baseDomain)
|
||||
|
||||
// 초기화
|
||||
let inspectionData
|
||||
|
||||
// 3. 캐시된 데이터가 없거나 만료되었을 때만 API 호출
|
||||
if (cache.has(cacheKey)) {
|
||||
inspectionData = cache.get(cacheKey) as WebInspectionData
|
||||
} else {
|
||||
const apiUrl = `${iBaseApiUrl}/pub-comm/v3.0/inspection/${gameId}`
|
||||
// 직접 $fetch 사용 (composable 사용하지 않음)
|
||||
const response = await $fetch<ResGetInspectionData>(apiUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
inspectionData = response?.value?.inspection as WebInspectionData
|
||||
console.log("🚀 00000 inspectionData:", inspectionData)
|
||||
cache.set(cacheKey, 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)
|
||||
|
||||
// 5. 점검 상태별 캐시 설정
|
||||
if (inspectionData?.inspection_status === 1 && currentTime >= tsStartDate && currentTime <= tsEndDate) {
|
||||
/**
|
||||
* 점검 중인 경우
|
||||
* - 점검 상태가 1이고 현재 시간이 점검 시작과 종료 사이에 있는지 확인
|
||||
* - 점검 URL 경로가 아닐 경우 no-cache 설정
|
||||
* - 화이트 리스트 체크
|
||||
*/
|
||||
// 점검 url path 가 아닐 경우, 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_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()
|
||||
}
|
||||
} 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 (e) {
|
||||
console.error('[Exception] /server/middleware/middleware-02-global: ', e)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user