服务器配置

This commit is contained in:
tianmo committed 2026-10-17 16:09:31 +08:00
1 parent a1b4a63552
commit fa7c8244b0
33 files changed
+1882 -363

No files matched your search

+157 -11
View File
@@ -10,6 +10,12 @@ import {
SETTINGS_STORAGE_USE_DATE_PATH,
SETTINGS_IMAGE_BASE_URL,
SETTINGS_SITE_BASE_URL,
SETTINGS_VIDEO_BASE_URL,
SETTINGS_FILE_BASE_URL,
SETTINGS_DOMAIN_SEPARATION,
SETTINGS_IMAGE_DOMAIN_SEPARATED,
SETTINGS_VIDEO_DOMAIN_SEPARATED,
SETTINGS_FILE_DOMAIN_SEPARATED,
SETTINGS_FILE_BED_ACCESS,
SETTINGS_WEBP_QUALITY,
SETTINGS_LOGIN_VERIFICATION_METHOD,
@@ -84,6 +90,14 @@ export function normalizeSiteBaseUrl(raw: string): string {
return normalizeBaseUrl(raw)
}
export function normalizeVideoBaseUrl(raw: string): string {
return normalizeBaseUrl(raw)
}
export function normalizeFileBaseUrl(raw: string): string {
return normalizeBaseUrl(raw)
}
export function hostnameFromBaseUrl(value: string): string | null {
if (!value) return null
try {
@@ -137,6 +151,38 @@ export function getSiteBaseUrlConfigured(event: H3Event): string {
return getSiteBaseUrlFromEnv(event)
}
export function getVideoBaseUrlFromEnv(event: H3Event): string {
return normalizeVideoBaseUrl(getRuntimeEnv(event, 'VIDEO_BASE_URL', 'videoBaseUrl'))
}
export function getVideoBaseUrlSource(event: H3Event): SettingSource {
if (getSetting(SETTINGS_VIDEO_BASE_URL) !== null) return 'db'
if (getVideoBaseUrlFromEnv(event)) return 'env'
return 'none'
}
export function getVideoBaseUrlConfigured(event: H3Event): string {
const dbRaw = getSetting(SETTINGS_VIDEO_BASE_URL)
if (dbRaw !== null) return normalizeVideoBaseUrl(dbRaw)
return getVideoBaseUrlFromEnv(event)
}
export function getFileBaseUrlFromEnv(event: H3Event): string {
return normalizeFileBaseUrl(getRuntimeEnv(event, 'FILE_BASE_URL', 'fileBaseUrl'))
}
export function getFileBaseUrlSource(event: H3Event): SettingSource {
if (getSetting(SETTINGS_FILE_BASE_URL) !== null) return 'db'
if (getFileBaseUrlFromEnv(event)) return 'env'
return 'none'
}
export function getFileBaseUrlConfigured(event: H3Event): string {
const dbRaw = getSetting(SETTINGS_FILE_BASE_URL)
if (dbRaw !== null) return normalizeFileBaseUrl(dbRaw)
return getFileBaseUrlFromEnv(event)
}
export function getSiteBaseUrl(event: H3Event): string {
const dbRaw = getSetting(SETTINGS_SITE_BASE_URL)
if (dbRaw !== null) {
@@ -155,6 +201,15 @@ export function getSiteBaseUrl(event: H3Event): string {
}
export function getImageBaseUrl(event: H3Event): string {
if (isDomainSeparationActive(event) && !isImageDomainSeparated(event)) {
return getSiteBaseUrl(event)
}
if (
getSetting(SETTINGS_DOMAIN_SEPARATION) === 'false'
&& getSetting(SETTINGS_IMAGE_DOMAIN_SEPARATED) === 'false'
) {
return getSiteBaseUrl(event)
}
const dbRaw = getSetting(SETTINGS_IMAGE_BASE_URL)
if (dbRaw !== null) {
const configured = normalizeImageBaseUrl(dbRaw)
@@ -172,6 +227,64 @@ export function getImageBaseUrl(event: H3Event): string {
return requestUrl.origin
}
export function getVideoBaseUrl(event: H3Event): string {
if (isDomainSeparationActive(event) && !isVideoDomainSeparated(event)) {
return getSiteBaseUrl(event)
}
if (
getSetting(SETTINGS_DOMAIN_SEPARATION) === 'false'
&& getSetting(SETTINGS_VIDEO_DOMAIN_SEPARATED) === 'false'
) {
return getSiteBaseUrl(event)
}
return getVideoBaseUrlConfigured(event) || getImageBaseUrl(event)
}
export function getFileBaseUrl(event: H3Event): string {
if (isDomainSeparationActive(event) && !isFileDomainSeparated(event)) {
return getSiteBaseUrl(event)
}
if (
getSetting(SETTINGS_DOMAIN_SEPARATION) === 'false'
&& getSetting(SETTINGS_FILE_DOMAIN_SEPARATED) === 'false'
) {
return getSiteBaseUrl(event)
}
return getFileBaseUrlConfigured(event) || getImageBaseUrl(event)
}
export function getMediaBaseUrl(event: H3Event, key: string): string {
if (key.startsWith('videos/')) return getVideoBaseUrl(event)
if (key.startsWith('files/')) return getFileBaseUrl(event)
return getImageBaseUrl(event)
}
function parseStoredBoolean(raw: string | null): boolean | null {
if (raw === null) return null
return raw === 'true'
}
export function isImageDomainSeparated(event: H3Event): boolean {
if (!isDomainSeparationActive(event)) return false
const explicit = parseStoredBoolean(getSetting(SETTINGS_IMAGE_DOMAIN_SEPARATED))
if (explicit !== null) return explicit
return Boolean(getImageBaseUrlConfigured(event))
}
export function isVideoDomainSeparated(event: H3Event): boolean {
if (!isDomainSeparationActive(event)) return false
const explicit = parseStoredBoolean(getSetting(SETTINGS_VIDEO_DOMAIN_SEPARATED))
if (explicit !== null) return explicit
return Boolean(getVideoBaseUrlConfigured(event))
}
export function isFileDomainSeparated(event: H3Event): boolean {
if (!isDomainSeparationActive(event)) return false
const explicit = parseStoredBoolean(getSetting(SETTINGS_FILE_DOMAIN_SEPARATED))
if (explicit !== null) return explicit
return Boolean(getFileBaseUrlConfigured(event))
}
export function isHideFolderInUrlFromEnv(): boolean {
try {
return process.env.HIDE_FOLDER_IN_URL?.trim().toLowerCase() === 'true'
@@ -555,26 +668,32 @@ export function isValidSiteBaseUrl(value: string): boolean {
export function validateDomainSeparationPair(
siteBaseUrl: string,
imageBaseUrl: string
imageBaseUrl: string,
videoBaseUrl = '',
fileBaseUrl = ''
): string | null {
if (!siteBaseUrl || !imageBaseUrl) {
return '启用域名分离时需同时填写网站域名与图片域名'
return '启用域名分离时需同时填写后台域名与图片域名'
}
if (!isValidSiteBaseUrl(siteBaseUrl) || !isValidImageBaseUrl(imageBaseUrl)) {
if (
!isValidSiteBaseUrl(siteBaseUrl)
|| !isValidImageBaseUrl(imageBaseUrl)
|| (videoBaseUrl && !isValidImageBaseUrl(videoBaseUrl))
|| (fileBaseUrl && !isValidImageBaseUrl(fileBaseUrl))
) {
return '域名需为 http(s) 地址,且末尾不加 /'
}
const siteHost = hostnameFromBaseUrl(siteBaseUrl)
const imageHost = hostnameFromBaseUrl(imageBaseUrl)
if (!siteHost || !imageHost) {
const videoHost = hostnameFromBaseUrl(videoBaseUrl)
const fileHost = hostnameFromBaseUrl(fileBaseUrl)
if (!siteHost || !imageHost || (videoBaseUrl && !videoHost) || (fileBaseUrl && !fileHost)) {
return '域名格式无效'
}
if (siteHost === imageHost) {
return '网站域名与图片域名不能使用相同主机名'
}
return null
}
export type RequestHostRole = 'site' | 'image' | 'unknown' | 'single'
export type RequestHostRole = 'site' | 'image' | 'video' | 'file' | 'unknown' | 'single'
export function getIsolationRequestHost(event: H3Event): string {
const requestUrl = getRequestURL(event, {
@@ -587,8 +706,12 @@ export function getIsolationRequestHost(event: H3Event): string {
export function validateSettingsDomainPatch(input: {
existingSite: string
existingImage: string
existingVideo?: string
existingFile?: string
nextSite: string
nextImage: string
nextVideo?: string
nextFile?: string
wantSeparation: boolean | null
siteBaseUrlProvided: boolean
}): string | null {
@@ -597,6 +720,8 @@ export function validateSettingsDomainPatch(input: {
existingImage,
nextSite,
nextImage,
nextVideo = '',
nextFile = '',
wantSeparation,
siteBaseUrlProvided
} = input
@@ -609,9 +734,9 @@ export function validateSettingsDomainPatch(input: {
if (wantSeparation === true) {
if (!nextSite || !nextImage) {
return '启用域名分离时需同时填写网站域名与图片域名'
return '启用域名分离时需同时填写后台域名与图片域名'
}
return validateDomainSeparationPair(nextSite, nextImage)
return validateDomainSeparationPair(nextSite, nextImage, nextVideo, nextFile)
}
if (wantSeparation === false) {
@@ -623,7 +748,7 @@ export function validateSettingsDomainPatch(input: {
}
if (nextSite && nextImage) {
return validateDomainSeparationPair(nextSite, nextImage)
return validateDomainSeparationPair(nextSite, nextImage, nextVideo, nextFile)
}
return null
@@ -647,12 +772,18 @@ export function getRequestRuntime(event: H3Event) {
const siteHost = hostnameFromBaseUrl(getSiteBaseUrlConfigured(event))
const imageHost = hostnameFromBaseUrl(getImageBaseUrlConfigured(event))
const videoHost = hostnameFromBaseUrl(getVideoBaseUrl(event))
const fileHost = hostnameFromBaseUrl(getFileBaseUrl(event))
let hostRole: RequestHostRole = 'unknown'
if (currentHost === siteHost) {
hostRole = 'site'
} else if (currentHost === imageHost) {
hostRole = 'image'
} else if (currentHost === videoHost) {
hostRole = 'video'
} else if (currentHost === fileHost) {
hostRole = 'file'
}
return {
@@ -668,8 +799,12 @@ export function getSettingsPayload(event: H3Event) {
const allowedRefererHosts = getAllowedRefererHostsRaw(event)
const siteBaseUrlConfigured = getSiteBaseUrlConfigured(event)
const imageBaseUrlConfigured = getImageBaseUrlConfigured(event)
const videoBaseUrlConfigured = getVideoBaseUrlConfigured(event)
const fileBaseUrlConfigured = getFileBaseUrlConfigured(event)
const effectiveSiteBaseUrl = getSiteBaseUrl(event)
const effectiveImageBaseUrl = getImageBaseUrl(event)
const effectiveVideoBaseUrl = getVideoBaseUrl(event)
const effectiveFileBaseUrl = getFileBaseUrl(event)
const autoDeleteDays = getAutoDeleteDays(event)
const appVersion = config.appVersion as string
const domainSeparation = isDomainSeparationActive(event)
@@ -693,8 +828,17 @@ export function getSettingsPayload(event: H3Event) {
imageBaseUrl: imageBaseUrlConfigured,
imageBaseUrlConfigured,
imageBaseUrlSource: getImageBaseUrlSource(event),
imageDomainSeparated: isImageDomainSeparated(event),
videoBaseUrl: videoBaseUrlConfigured,
videoBaseUrlSource: getVideoBaseUrlSource(event),
videoDomainSeparated: isVideoDomainSeparated(event),
fileBaseUrl: fileBaseUrlConfigured,
fileBaseUrlSource: getFileBaseUrlSource(event),
fileDomainSeparated: isFileDomainSeparated(event),
effectiveSiteBaseUrl,
effectiveImageBaseUrl,
effectiveVideoBaseUrl,
effectiveFileBaseUrl,
domainSeparation,
runtime: getRequestRuntime(event),
hideFolderInUrl,
@@ -725,6 +869,8 @@ export function getSettingsPayload(event: H3Event) {
}
export function isDomainSeparationActive(event: H3Event): boolean {
const explicit = getSetting(SETTINGS_DOMAIN_SEPARATION)
if (explicit !== null) return explicit === 'true'
const siteHost = hostnameFromBaseUrl(getSiteBaseUrlConfigured(event))
const imageHost = hostnameFromBaseUrl(getImageBaseUrlConfigured(event))
return Boolean(siteHost && imageHost && siteHost !== imageHost)