fix. 인트로 리다이렉트 수정
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
export default defineNuxtRouteMiddleware(to => {
|
||||
// server에서는 실행X -----
|
||||
if (import.meta.server) return
|
||||
|
||||
// error 페이지는 실행X -----
|
||||
if (to.path.includes('/error')) return
|
||||
|
||||
@@ -11,73 +14,31 @@ export default defineNuxtRouteMiddleware(to => {
|
||||
// app.vue에서 설정한 스토어 값이 없으면 대기
|
||||
if (!gameData.value) return
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Home Redirect]
|
||||
// -------------------------------------------------------------------------------
|
||||
const gamePath = getPathAfterLanguage(to.path)
|
||||
const langCode = import.meta.client
|
||||
? csrGetFinalLocale(to.path, gameData.value.lang_codes)
|
||||
: ssrGetFinalLocale(
|
||||
to.path,
|
||||
useRequestHeaders(['accept-language']),
|
||||
gameData.value.lang_codes,
|
||||
gameData.value.default_lang_code
|
||||
)
|
||||
const langCode = csrGetFinalLocale(to.path, gameData.value.lang_codes)
|
||||
|
||||
const isRootPath = gamePath === '' || gamePath === '/'
|
||||
|
||||
if (isRootPath) {
|
||||
// gameData.intro.page_url이 있으면 해당 URL로 리다이렉트, 없으면 /home으로
|
||||
const introPageUrl = gameData.value?.intro?.page_url
|
||||
let defaultPath = `/${langCode}/home`
|
||||
|
||||
if (introPageUrl && introPageUrl.trim() !== '') {
|
||||
// 외부 URL인지 확인
|
||||
const isExternalUrl =
|
||||
introPageUrl.startsWith('http://') ||
|
||||
introPageUrl.startsWith('https://')
|
||||
|
||||
if (isExternalUrl) {
|
||||
// 외부 URL인 경우 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
} else {
|
||||
// 내부 경로인 경우 언어 코드 패턴 확인
|
||||
const normalizedIntroUrl = introPageUrl.split('?')[0] // 쿼리스트링 제외
|
||||
const languagePattern = /^\/[a-z]{2}(-[a-z]{2})?(\/|$)/
|
||||
const hasLanguageCode = languagePattern.test(normalizedIntroUrl)
|
||||
|
||||
if (hasLanguageCode) {
|
||||
// 이미 언어 코드가 있으면 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
} else {
|
||||
// 언어 코드가 없으면 추가
|
||||
const pathWithSlash = normalizedIntroUrl.startsWith('/')
|
||||
? normalizedIntroUrl
|
||||
: `/${normalizedIntroUrl}`
|
||||
defaultPath = `/${langCode}${pathWithSlash}`
|
||||
|
||||
// 쿼리스트링이 있으면 다시 추가
|
||||
if (introPageUrl.includes('?')) {
|
||||
defaultPath += '?' + introPageUrl.split('?')[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const redirectPath = getIntroRedirectPath(
|
||||
introPageUrl,
|
||||
langCode,
|
||||
to.fullPath
|
||||
)
|
||||
|
||||
// 무한 리다이렉트 방지: 현재 경로와 리다이렉트할 URL 비교
|
||||
const normalizedFinalUrl = defaultPath.split('?')[0] // 쿼리스트링 제외
|
||||
const currentPath = to.path
|
||||
const isExternalUrl =
|
||||
defaultPath.startsWith('http://') || defaultPath.startsWith('https://')
|
||||
const isSamePath = !isExternalUrl && currentPath === normalizedFinalUrl
|
||||
const normalizedFinalUrl = redirectPath.split('?')[0] // 리다이렉트 경로 (쿼리스트링 제외)
|
||||
const isExternal = isExternalUrl(redirectPath)
|
||||
const isSamePath = !isExternal && to.path === normalizedFinalUrl
|
||||
|
||||
if (!isSamePath) {
|
||||
// 다른 경로에서 접근한 경우에만 리다이렉트
|
||||
const queryString = to.fullPath.includes('?')
|
||||
? '?' + to.fullPath.split('?')[1]
|
||||
: ''
|
||||
const redirectUrl =
|
||||
queryString && !defaultPath.includes('?')
|
||||
? `${defaultPath}${queryString}`
|
||||
: defaultPath
|
||||
return navigateTo(redirectUrl, { external: isExternalUrl })
|
||||
return navigateTo(redirectPath, { external: isExternal })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import { defineEventHandler, createError, setCookie, type H3Event } from 'h3'
|
||||
import { getGameDomain, getPathLocale } from '#layers/utils/urlUtil'
|
||||
import {
|
||||
getGameDomain,
|
||||
getPathLocale,
|
||||
getIntroRedirectPath,
|
||||
isExternalUrl,
|
||||
getPathAfterLanguage,
|
||||
} from '#layers/utils/urlUtil'
|
||||
import { ssrGetFinalLocale } from '#layers/utils/localeUtil'
|
||||
import { isStaticFile } from '#layers/utils/commonUtil'
|
||||
import { getTrueClientIp } from '#layers/utils/apiUtil'
|
||||
@@ -327,6 +333,39 @@ export default defineEventHandler(async event => {
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Home Redirect]
|
||||
// -------------------------------------------------------------------------------
|
||||
if (!event.node.res.headersSent && !event.node.res.writableEnded) {
|
||||
const gamePath = getPathAfterLanguage(fullPath)
|
||||
const isRootPath = gamePath === '' || gamePath === '/'
|
||||
|
||||
if (isRootPath) {
|
||||
// gameData.intro.page_url이 있으면 해당 URL로 리다이렉트, 없으면 /home으로
|
||||
const introPageUrl = gameDataValue?.intro?.page_url
|
||||
const currentFullUrl = event.node.req.url || fullPath
|
||||
const redirectPath = getIntroRedirectPath(
|
||||
introPageUrl,
|
||||
finalLocale,
|
||||
currentFullUrl
|
||||
)
|
||||
|
||||
// 무한 리다이렉트 방지: 현재 경로와 리다이렉트할 URL 비교
|
||||
const currentPathOnly = fullPath.split('?')[0] // 현재 경로 (쿼리스트링 제외)
|
||||
const normalizedFinalUrl = redirectPath.split('?')[0] // 리다이렉트 경로 (쿼리스트링 제외)
|
||||
const isExternal = isExternalUrl(redirectPath)
|
||||
const isSamePath =
|
||||
!isExternal && currentPathOnly === normalizedFinalUrl
|
||||
|
||||
if (!isSamePath) {
|
||||
// 다른 경로에서 접근한 경우에만 리다이렉트
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', redirectPath)
|
||||
event.node.res.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 정상 접속 허용
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { getHeader, getRequestHost, type H3Event } from 'h3'
|
||||
|
||||
/**
|
||||
* URL이 외부 URL인지 확인하는 함수
|
||||
* @param url - 확인할 URL 문자열
|
||||
* @returns 외부 URL 여부
|
||||
*/
|
||||
export const isExternalUrl = (url: string): boolean => {
|
||||
return url.startsWith('http://') || url.startsWith('https://')
|
||||
}
|
||||
|
||||
/**
|
||||
* 게임 도메인을 가져오는 컴포저블 함수
|
||||
* 서버와 클라이언트 환경에서 모두 동작
|
||||
@@ -48,41 +57,92 @@ export const getGameDomain = (event?: H3Event): string => {
|
||||
* @param url - URL 문자열
|
||||
* @returns 언어 코드 문자열
|
||||
*/
|
||||
export const getPathLocale = (url?: string): string => {
|
||||
const targetUrl = url || (import.meta.client ? window.location.pathname : '')
|
||||
const cleanTargetUrl = targetUrl.endsWith('/')
|
||||
? targetUrl.slice(0, -1)
|
||||
: targetUrl
|
||||
export const getPathLocale = (url: string): string => {
|
||||
if (!url) return ''
|
||||
|
||||
return cleanTargetUrl.split('/')[1]
|
||||
const cleanUrl = url.endsWith('/') ? url.slice(0, -1) : url
|
||||
return cleanUrl.split('/')[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* URL에서 언어 코드 이후의 경로를 추출하는 함수
|
||||
* @param url - URL 문자열
|
||||
* @param url - URL 문자열 (서버에서는 필수)
|
||||
* @returns 언어 코드 이후의 경로 문자열
|
||||
*/
|
||||
export const getPathAfterLanguage = (url?: string): string => {
|
||||
const targetUrl = url || (import.meta.client ? window.location.pathname : '')
|
||||
const cleanTargetUrl = targetUrl.endsWith('/')
|
||||
? targetUrl.slice(0, -1)
|
||||
: targetUrl
|
||||
export const getPathAfterLanguage = (url: string): string => {
|
||||
if (!url) return ''
|
||||
|
||||
const cleanUrl = url.split('?')[0].endsWith('/') ? url.slice(0, -1) : url
|
||||
|
||||
// URL에서 언어 코드 패턴을 찾아서 그 뒤의 경로를 추출
|
||||
// 예: /ko/about/story -> /about/story
|
||||
// 예: /ko -> "" (빈 문자열)
|
||||
const languagePattern = /^\/[a-z]{2}(-[a-z]{2})?\/(.+)$/
|
||||
const match = cleanTargetUrl.match(languagePattern)
|
||||
const match = cleanUrl.match(languagePattern)
|
||||
if (match && match[2]) {
|
||||
return `/${match[2]}`
|
||||
} else {
|
||||
// 언어 코드만 있고 뒤에 아무것도 없는 경우 (예: /ko, /en, /zh-tw, /zh-cn)
|
||||
const languageOnlyPattern = /^\/[a-z]{2}(-[a-z]{2})?$/
|
||||
if (languageOnlyPattern.test(cleanTargetUrl)) {
|
||||
if (languageOnlyPattern.test(cleanUrl)) {
|
||||
return ''
|
||||
} else {
|
||||
// 언어 코드가 없는 경우 원본 경로 그대로 반환 (이미 /로 시작)
|
||||
return cleanTargetUrl
|
||||
return cleanUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* intro page_url을 기반으로 리다이렉트 경로를 생성하는 공통 함수
|
||||
* @param introPageUrl - intro.page_url 값
|
||||
* @param langCode - 현재 언어 코드
|
||||
* @param currentUrl - 현재 접근한 URL (쿼리스트링 포함, 옵션)
|
||||
* @returns 최종 리다이렉트 경로 (없으면 기본값 /home)
|
||||
*/
|
||||
export const getIntroRedirectPath = (
|
||||
introPageUrl: string | undefined | null,
|
||||
langCode: string,
|
||||
currentUrl?: string
|
||||
): string => {
|
||||
// 기본값: /langCode/home
|
||||
let defaultPath = `/${langCode}/home`
|
||||
|
||||
if (introPageUrl && introPageUrl.trim() !== '') {
|
||||
if (isExternalUrl(introPageUrl)) {
|
||||
// 외부 URL인 경우 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
} else {
|
||||
// 내부 경로인 경우 언어 코드 패턴 확인
|
||||
const normalizedIntroUrl = introPageUrl.split('?')[0] // 쿼리스트링 제외
|
||||
const languagePattern = /^\/[a-z]{2}(-[a-z]{2})?(\/|$)/
|
||||
const hasLanguageCode = languagePattern.test(normalizedIntroUrl)
|
||||
|
||||
if (hasLanguageCode) {
|
||||
// 이미 언어 코드가 있으면 그대로 사용
|
||||
defaultPath = introPageUrl
|
||||
} else {
|
||||
// 언어 코드가 없으면 추가
|
||||
const pathWithSlash = normalizedIntroUrl.startsWith('/')
|
||||
? normalizedIntroUrl
|
||||
: `/${normalizedIntroUrl}`
|
||||
defaultPath = `/${langCode}${pathWithSlash}`
|
||||
|
||||
// 쿼리스트링이 있으면 다시 추가
|
||||
if (introPageUrl.includes('?')) {
|
||||
defaultPath += '?' + introPageUrl.split('?')[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intro에 쿼리스트링이 없고, 현재 URL에 쿼리스트링이 있으면 추가
|
||||
if (!defaultPath.includes('?') && currentUrl && currentUrl.includes('?')) {
|
||||
const queryString = currentUrl.split('?')[1]
|
||||
if (queryString) {
|
||||
defaultPath += '?' + queryString
|
||||
}
|
||||
}
|
||||
|
||||
return defaultPath
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user