Files
BYW-VoiceHub/patches/0030-fix-system-settings-DB-SYSTEM_SETTINGS_DEFAULTS-Redi.patch
T

724 lines
26 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
From 13b9b721700c88cd713bdcd2a28240673a7b557a Mon Sep 17 00:00:00 2001
From: HenryDu8133 <813367384@qq.com>
Date: Fri, 11 Sep 2026 21:39:45 +0800
Subject: [PATCH 30/58] =?UTF-8?q?fix(system-settings):=20=E4=BF=AE?=
=?UTF-8?q?=E5=A4=8D=E7=BA=BF=E4=B8=8A=E4=B8=BB=E9=A2=98/=E6=8E=92?=
=?UTF-8?q?=E6=9C=9F/=E8=81=9A=E5=90=88=E7=99=BB=E5=BD=95=E7=AD=89?=
=?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=BC=BA=E5=A4=B1=EF=BC=8C=E5=AF=B9=E6=96=B0?=
=?UTF-8?q?=20DB=20=E5=AD=97=E6=AE=B5=E7=94=A8=20SYSTEM=5FSETTINGS=5FDEFAU?=
=?UTF-8?q?LTS=20=E5=85=9C=E5=BA=95=20+=20=E6=9B=B4=E6=96=B0=E6=8E=A5?=
=?UTF-8?q?=E5=8F=A3=E5=85=81=E8=AE=B8=E5=86=99=E5=85=A5=E6=96=B0=E5=AD=97?=
=?UTF-8?q?=E6=AE=B5=20+=20=E7=BC=A9=E7=9F=AD=20Redis=20=E7=BC=93=E5=AD=98?=
=?UTF-8?q?=20TTL?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- site-config.get.ts:filterPublicSettings 和 Redis/DB 命中分支全部先合并 SYSTEM_SETTINGS_DEFAULTS,解决老库无 schema 新字段导致 public 返回 defaultTheme=light、enabledThemes=light,dark 等旧值的问题
- admin/system-settings/index.post.ts:新增 ~40 个白名单字段(主题/排期可见范围/留言自动审查/OAuth 聚合/首次强制改密/投稿限制/卡密/注册审核/自动备份/统计代码等),支持管理面板保存生效;更新返回结果用 fillDefaults(DEFAULTS) 兜底
- admin/system-settings/index.ts (GET):返回 DB 记录前合并 SYSTEM_SETTINGS_DEFAULTS,避免管理后台 UI 拿不到新字段
- cacheService.ts:CACHE_TTL.SYSTEM_SETTINGS 3600s → 900s(60min→15min),防止 Redis 单机挂或旧脏值导致配置变更长时间不生效
---
.../api/admin/system-settings/index.post.ts | 575 +++++++++++++++++-
server/api/admin/system-settings/index.ts | 5 +-
server/api/site-config.get.ts | 13 +-
server/services/cacheService.ts | 2 +-
4 files changed, 585 insertions(+), 10 deletions(-)
diff --git a/server/api/admin/system-settings/index.post.ts b/server/api/admin/system-settings/index.post.ts
index b33c4fe..9f2ac77 100644
--- a/server/api/admin/system-settings/index.post.ts
+++ b/server/api/admin/system-settings/index.post.ts
@@ -198,6 +198,520 @@ export default defineEventHandler(async (event) => {
updateData.forceBlockAllRequests = body.forceBlockAllRequests
}
+ if (body.submissionNoteRequiresApproval !== undefined) {
+ if (typeof body.submissionNoteRequiresApproval !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionNoteRequiresApproval 必须是布尔值'
+ })
+ }
+ updateData.submissionNoteRequiresApproval = body.submissionNoteRequiresApproval
+ }
+
+ if (body.telemetryEnabled !== undefined) {
+ if (typeof body.telemetryEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'telemetryEnabled 必须是布尔值'
+ })
+ }
+ updateData.telemetryEnabled = body.telemetryEnabled
+ }
+
+ if (body.showBeianIcon !== undefined) {
+ if (typeof body.showBeianIcon !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'showBeianIcon 必须是布尔值'
+ })
+ }
+ updateData.showBeianIcon = body.showBeianIcon
+ }
+
+ if (body.scheduleDaysDisplay !== undefined) {
+ if (body.scheduleDaysDisplay !== null && (!Number.isInteger(body.scheduleDaysDisplay) || body.scheduleDaysDisplay < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysDisplay 必须是非负整数或null'
+ })
+ }
+ updateData.scheduleDaysDisplay = body.scheduleDaysDisplay
+ }
+
+ if (body.scheduleDaysEditable !== undefined) {
+ if (body.scheduleDaysEditable !== null && (!Number.isInteger(body.scheduleDaysEditable) || body.scheduleDaysEditable < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysEditable 必须是非负整数或null'
+ })
+ }
+ updateData.scheduleDaysEditable = body.scheduleDaysEditable
+ }
+
+ if (body.scheduleDaysRequestable !== undefined) {
+ if (body.scheduleDaysRequestable !== null && (!Number.isInteger(body.scheduleDaysRequestable) || body.scheduleDaysRequestable < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysRequestable 必须是非负整数或null'
+ })
+ }
+ updateData.scheduleDaysRequestable = body.scheduleDaysRequestable
+ }
+
+ if (body.scheduleDaysBeforeEnabled !== undefined) {
+ if (typeof body.scheduleDaysBeforeEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysBeforeEnabled 必须是布尔值'
+ })
+ }
+ updateData.scheduleDaysBeforeEnabled = body.scheduleDaysBeforeEnabled
+ }
+
+ if (body.scheduleDaysBefore !== undefined) {
+ if (body.scheduleDaysBefore !== null && (!Number.isInteger(body.scheduleDaysBefore) || body.scheduleDaysBefore < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysBefore 必须是非负整数或null'
+ })
+ }
+ updateData.scheduleDaysBefore = body.scheduleDaysBefore
+ }
+
+ if (body.scheduleDaysAfterEnabled !== undefined) {
+ if (typeof body.scheduleDaysAfterEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysAfterEnabled 必须是布尔值'
+ })
+ }
+ updateData.scheduleDaysAfterEnabled = body.scheduleDaysAfterEnabled
+ }
+
+ if (body.scheduleDaysAfter !== undefined) {
+ if (body.scheduleDaysAfter !== null && (!Number.isInteger(body.scheduleDaysAfter) || body.scheduleDaysAfter < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'scheduleDaysAfter 必须是非负整数或null'
+ })
+ }
+ updateData.scheduleDaysAfter = body.scheduleDaysAfter
+ }
+
+ if (body.forcePasswordChangeOnFirstLogin !== undefined) {
+ if (typeof body.forcePasswordChangeOnFirstLogin !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'forcePasswordChangeOnFirstLogin 必须是布尔值'
+ })
+ }
+ updateData.forcePasswordChangeOnFirstLogin = body.forcePasswordChangeOnFirstLogin
+ }
+
+ if (body.enableSubmissionRestriction !== undefined) {
+ if (typeof body.enableSubmissionRestriction !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'enableSubmissionRestriction 必须是布尔值'
+ })
+ }
+ updateData.enableSubmissionRestriction = body.enableSubmissionRestriction
+ }
+
+ if (body.submissionRestrictionScope !== undefined) {
+ updateData.submissionRestrictionScope = body.submissionRestrictionScope
+ }
+
+ if (body.cardCodeEnabled !== undefined) {
+ if (typeof body.cardCodeEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'cardCodeEnabled 必须是布尔值'
+ })
+ }
+ updateData.cardCodeEnabled = body.cardCodeEnabled
+ }
+
+ if (body.cardCodeSubmissionRequired !== undefined) {
+ if (typeof body.cardCodeSubmissionRequired !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'cardCodeSubmissionRequired 必须是布尔值'
+ })
+ }
+ updateData.cardCodeSubmissionRequired = body.cardCodeSubmissionRequired
+ }
+
+ if (body.cardCodeVoteEnabled !== undefined) {
+ if (typeof body.cardCodeVoteEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'cardCodeVoteEnabled 必须是布尔值'
+ })
+ }
+ updateData.cardCodeVoteEnabled = body.cardCodeVoteEnabled
+ }
+
+ if (body.cardCodeReplayEnabled !== undefined) {
+ if (typeof body.cardCodeReplayEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'cardCodeReplayEnabled 必须是布尔值'
+ })
+ }
+ updateData.cardCodeReplayEnabled = body.cardCodeReplayEnabled
+ }
+
+ if (body.enableCardCodeRequests !== undefined) {
+ if (typeof body.enableCardCodeRequests !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'enableCardCodeRequests 必须是布尔值'
+ })
+ }
+ updateData.enableCardCodeRequests = body.enableCardCodeRequests
+ }
+
+ if (body.requireCardCodeForRequests !== undefined) {
+ if (typeof body.requireCardCodeForRequests !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'requireCardCodeForRequests 必须是布尔值'
+ })
+ }
+ updateData.requireCardCodeForRequests = body.requireCardCodeForRequests
+ }
+
+ if (body.enableCardCodeLimitBypass !== undefined) {
+ if (typeof body.enableCardCodeLimitBypass !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'enableCardCodeLimitBypass 必须是布尔值'
+ })
+ }
+ updateData.enableCardCodeLimitBypass = body.enableCardCodeLimitBypass
+ }
+
+ if (body.submissionRestrictionEnabled !== undefined) {
+ if (typeof body.submissionRestrictionEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionRestrictionEnabled 必须是布尔值'
+ })
+ }
+ updateData.submissionRestrictionEnabled = body.submissionRestrictionEnabled
+ }
+
+ if (body.submissionRestrictionGradeEnabled !== undefined) {
+ if (typeof body.submissionRestrictionGradeEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionRestrictionGradeEnabled 必须是布尔值'
+ })
+ }
+ updateData.submissionRestrictionGradeEnabled = body.submissionRestrictionGradeEnabled
+ }
+
+ if (body.submissionRestrictionClassEnabled !== undefined) {
+ if (typeof body.submissionRestrictionClassEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionRestrictionClassEnabled 必须是布尔值'
+ })
+ }
+ updateData.submissionRestrictionClassEnabled = body.submissionRestrictionClassEnabled
+ }
+
+ if (body.submissionRestrictionGraduateEnabled !== undefined) {
+ if (typeof body.submissionRestrictionGraduateEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionRestrictionGraduateEnabled 必须是布尔值'
+ })
+ }
+ updateData.submissionRestrictionGraduateEnabled = body.submissionRestrictionGraduateEnabled
+ }
+
+ if (body.submissionRestrictionWithdrawnEnabled !== undefined) {
+ if (typeof body.submissionRestrictionWithdrawnEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'submissionRestrictionWithdrawnEnabled 必须是布尔值'
+ })
+ }
+ updateData.submissionRestrictionWithdrawnEnabled = body.submissionRestrictionWithdrawnEnabled
+ }
+
+ if (body.captchaProvider !== undefined) {
+ updateData.captchaProvider = body.captchaProvider
+ }
+
+ if (body.captchaEnabled !== undefined) {
+ if (typeof body.captchaEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'captchaEnabled 必须是布尔值'
+ })
+ }
+ updateData.captchaEnabled = body.captchaEnabled
+ }
+
+ if (body.allowRegister !== undefined) {
+ if (typeof body.allowRegister !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'allowRegister 必须是布尔值'
+ })
+ }
+ updateData.allowRegister = body.allowRegister
+ }
+
+ if (body.registerRequireEmail !== undefined) {
+ if (typeof body.registerRequireEmail !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'registerRequireEmail 必须是布尔值'
+ })
+ }
+ updateData.registerRequireEmail = body.registerRequireEmail
+ }
+
+ if (body.registerRequireEmailVerify !== undefined) {
+ if (typeof body.registerRequireEmailVerify !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'registerRequireEmailVerify 必须是布尔值'
+ })
+ }
+ updateData.registerRequireEmailVerify = body.registerRequireEmailVerify
+ }
+
+ if (body.registerDefaultRole !== undefined) {
+ updateData.registerDefaultRole = body.registerDefaultRole
+ }
+
+ if (body.registerRequiresApproval !== undefined) {
+ if (typeof body.registerRequiresApproval !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'registerRequiresApproval 必须是布尔值'
+ })
+ }
+ updateData.registerRequiresApproval = body.registerRequiresApproval
+ }
+
+ if (body.oauthRegisterRequiresApproval !== undefined) {
+ if (typeof body.oauthRegisterRequiresApproval !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'oauthRegisterRequiresApproval 必须是布尔值'
+ })
+ }
+ updateData.oauthRegisterRequiresApproval = body.oauthRegisterRequiresApproval
+ }
+
+ if (body.registerEmailRequired !== undefined) {
+ if (typeof body.registerEmailRequired !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'registerEmailRequired 必须是布尔值'
+ })
+ }
+ updateData.registerEmailRequired = body.registerEmailRequired
+ }
+
+ if (body.registerRequiresGradeClass !== undefined) {
+ if (typeof body.registerRequiresGradeClass !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'registerRequiresGradeClass 必须是布尔值'
+ })
+ }
+ updateData.registerRequiresGradeClass = body.registerRequiresGradeClass
+ }
+
+ if (body.oauthEnabled !== undefined) {
+ if (typeof body.oauthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'oauthEnabled 必须是布尔值'
+ })
+ }
+ updateData.oauthEnabled = body.oauthEnabled
+ }
+
+ if (body.oauthAllowRegister !== undefined) {
+ if (typeof body.oauthAllowRegister !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'oauthAllowRegister 必须是布尔值'
+ })
+ }
+ updateData.oauthAllowRegister = body.oauthAllowRegister
+ }
+
+ if (body.oauthAllowLogin !== undefined) {
+ if (typeof body.oauthAllowLogin !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'oauthAllowLogin 必须是布尔值'
+ })
+ }
+ updateData.oauthAllowLogin = body.oauthAllowLogin
+ }
+
+ if (body.oauthAllowBind !== undefined) {
+ if (typeof body.oauthAllowBind !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'oauthAllowBind 必须是布尔值'
+ })
+ }
+ updateData.oauthAllowBind = body.oauthAllowBind
+ }
+
+ if (body.allowOAuthRegistration !== undefined) {
+ if (typeof body.allowOAuthRegistration !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'allowOAuthRegistration 必须是布尔值'
+ })
+ }
+ updateData.allowOAuthRegistration = body.allowOAuthRegistration
+ }
+
+ if (body.oauthProviders !== undefined) {
+ updateData.oauthProviders = body.oauthProviders
+ }
+
+ if (body.githubOAuthEnabled !== undefined) {
+ if (typeof body.githubOAuthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'githubOAuthEnabled 必须是布尔值'
+ })
+ }
+ updateData.githubOAuthEnabled = body.githubOAuthEnabled
+ }
+
+ if (body.casdoorOAuthEnabled !== undefined) {
+ if (typeof body.casdoorOAuthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'casdoorOAuthEnabled 必须是布尔值'
+ })
+ }
+ updateData.casdoorOAuthEnabled = body.casdoorOAuthEnabled
+ }
+
+ if (body.googleOAuthEnabled !== undefined) {
+ if (typeof body.googleOAuthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'googleOAuthEnabled 必须是布尔值'
+ })
+ }
+ updateData.googleOAuthEnabled = body.googleOAuthEnabled
+ }
+
+ if (body.aggregateOAuthEnabled !== undefined) {
+ if (typeof body.aggregateOAuthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'aggregateOAuthEnabled 必须是布尔值'
+ })
+ }
+ updateData.aggregateOAuthEnabled = body.aggregateOAuthEnabled
+ }
+
+ if (body.aggregateOAuthLoginType !== undefined) {
+ updateData.aggregateOAuthLoginType = body.aggregateOAuthLoginType
+ }
+
+ if (body.customOAuthEnabled !== undefined) {
+ if (typeof body.customOAuthEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'customOAuthEnabled 必须是布尔值'
+ })
+ }
+ updateData.customOAuthEnabled = body.customOAuthEnabled
+ }
+
+ if (body.captchaMaxFailures !== undefined) {
+ if (body.captchaMaxFailures !== null && (!Number.isInteger(body.captchaMaxFailures) || body.captchaMaxFailures < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'captchaMaxFailures 必须是非负整数或null'
+ })
+ }
+ updateData.captchaMaxFailures = body.captchaMaxFailures
+ }
+
+ if (body.autoBackupEnabled !== undefined) {
+ if (typeof body.autoBackupEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'autoBackupEnabled 必须是布尔值'
+ })
+ }
+ updateData.autoBackupEnabled = body.autoBackupEnabled
+ }
+
+ if (body.autoBackupIntervalHours !== undefined) {
+ if (body.autoBackupIntervalHours !== null && (!Number.isInteger(body.autoBackupIntervalHours) || body.autoBackupIntervalHours < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'autoBackupIntervalHours 必须是非负整数或null'
+ })
+ }
+ updateData.autoBackupIntervalHours = body.autoBackupIntervalHours
+ }
+
+ if (body.autoBackupRetentionDays !== undefined) {
+ if (body.autoBackupRetentionDays !== null && (!Number.isInteger(body.autoBackupRetentionDays) || body.autoBackupRetentionDays < 0)) {
+ throw createError({
+ statusCode: 400,
+ message: 'autoBackupRetentionDays 必须是非负整数或null'
+ })
+ }
+ updateData.autoBackupRetentionDays = body.autoBackupRetentionDays
+ }
+
+ if (body.statisticsCodeEnabled !== undefined) {
+ if (typeof body.statisticsCodeEnabled !== 'boolean') {
+ throw createError({
+ statusCode: 400,
+ message: 'statisticsCodeEnabled 必须是布尔值'
+ })
+ }
+ updateData.statisticsCodeEnabled = body.statisticsCodeEnabled
+ }
+
+ if (body.statisticsCodeBody !== undefined) {
+ updateData.statisticsCodeBody = body.statisticsCodeBody
+ }
+
+ if (body.statisticsCodeHead !== undefined) {
+ updateData.statisticsCodeHead = body.statisticsCodeHead
+ }
+
+ if (body.defaultTheme !== undefined) {
+ updateData.defaultTheme =
+ typeof body.defaultTheme === 'string' && body.defaultTheme.trim()
+ ? body.defaultTheme.trim()
+ : null
+ }
+
+ if (body.enabledThemes !== undefined) {
+ updateData.enabledThemes =
+ body.enabledThemes === null || body.enabledThemes === undefined
+ ? null
+ : typeof body.enabledThemes === 'string'
+ ? body.enabledThemes
+ : JSON.stringify(body.enabledThemes)
+ }
+
// SMTP配置字段
if (body.smtpEnabled !== undefined) {
if (typeof body.smtpEnabled !== 'boolean') {
@@ -266,10 +780,69 @@ export default defineEventHandler(async (event) => {
})
}
+ const DEFAULTS = {
+ submissionNoteRequiresApproval: false,
+ telemetryEnabled: true,
+ showBeianIcon: true,
+ scheduleDaysDisplay: 7,
+ scheduleDaysEditable: 3,
+ scheduleDaysRequestable: 14,
+ scheduleDaysBeforeEnabled: false,
+ scheduleDaysBefore: 1,
+ scheduleDaysAfterEnabled: false,
+ scheduleDaysAfter: 1,
+ forcePasswordChangeOnFirstLogin: false,
+ enableSubmissionRestriction: false,
+ submissionRestrictionScope: 'all',
+ cardCodeEnabled: false,
+ cardCodeSubmissionRequired: false,
+ cardCodeVoteEnabled: false,
+ cardCodeReplayEnabled: false,
+ enableCardCodeRequests: false,
+ requireCardCodeForRequests: false,
+ enableCardCodeLimitBypass: false,
+ submissionRestrictionEnabled: false,
+ submissionRestrictionGradeEnabled: false,
+ submissionRestrictionClassEnabled: false,
+ submissionRestrictionGraduateEnabled: false,
+ submissionRestrictionWithdrawnEnabled: false,
+ captchaProvider: 'TURNSTILE',
+ captchaEnabled: false,
+ allowRegister: true,
+ registerRequireEmail: false,
+ registerRequireEmailVerify: false,
+ registerDefaultRole: 'USER',
+ registerRequiresApproval: false,
+ oauthRegisterRequiresApproval: false,
+ registerEmailRequired: false,
+ registerRequiresGradeClass: false,
+ oauthEnabled: false,
+ oauthAllowRegister: true,
+ oauthAllowLogin: true,
+ oauthAllowBind: true,
+ allowOAuthRegistration: false,
+ oauthProviders: '[]',
+ githubOAuthEnabled: false,
+ casdoorOAuthEnabled: false,
+ googleOAuthEnabled: false,
+ aggregateOAuthEnabled: false,
+ aggregateOAuthLoginType: 'qq',
+ customOAuthEnabled: false,
+ captchaMaxFailures: 3,
+ autoBackupEnabled: false,
+ autoBackupIntervalHours: 24,
+ autoBackupRetentionDays: 30,
+ statisticsCodeEnabled: false,
+ defaultTheme: 'System',
+ enabledThemes: '["System","ClassicDark","ClassicLight","ModernLight"]'
+} as const
+
// 获取当前设置,如果不存在则创建
const settingsResult = await db.select().from(systemSettings).limit(1)
let settings = settingsResult[0] || null
+ const fillDefaults = (obj: any) => ({ ...obj, ...Object.fromEntries(Object.entries(DEFAULTS).filter(([k]) => !Object.prototype.hasOwnProperty.call(obj || {}, k) || (obj && obj[k] === undefined || obj[k] === null))) }
+
if (!settings) {
// 如果不存在,创建新设置
const newSettingsResult = await db
@@ -324,7 +897,7 @@ export default defineEventHandler(async (event) => {
.set(updateData)
.where(eq(systemSettings.id, settings.id))
.returning()
- settings = updatedSettingsResult[0]
+ settings = fillDefaults(updatedSettingsResult[0])
}
// 清除系统设置缓存
diff --git a/server/api/admin/system-settings/index.ts b/server/api/admin/system-settings/index.ts
index cbe9256..8603bf9 100644
--- a/server/api/admin/system-settings/index.ts
+++ b/server/api/admin/system-settings/index.ts
@@ -1,6 +1,7 @@
import { db } from '~/drizzle/db'
import { systemSettings } from '~/drizzle/schema'
import { CacheService } from '../../../services/cacheService'
+import { SYSTEM_SETTINGS_DEFAULTS } from '~~/server/utils/system-settings-defaults'
export default defineEventHandler(async (event) => {
// 检查用户认证和权限
@@ -66,13 +67,15 @@ export default defineEventHandler(async (event) => {
// 将设置存入缓存
try {
+ const merged: any = { ...SYSTEM_SETTINGS_DEFAULTS, ...(settings || {}) }
+ settings = merged
await cacheService.setSystemSettings(settings)
} catch (cacheError) {
console.warn('缓存系统设置失败:', cacheError)
}
// 隐藏敏感字段
- if (settings.smtpPassword) {
+ if (settings && settings.smtpPassword) {
settings.smtpPassword = '****************'
}
diff --git a/server/api/site-config.get.ts b/server/api/site-config.get.ts
index 2e0c55c..d19a930 100644
--- a/server/api/site-config.get.ts
+++ b/server/api/site-config.get.ts
@@ -2,6 +2,7 @@ import { db } from '~/drizzle/db'
import { systemSettings } from '~/drizzle/schema'
import { cacheService } from '../services/cacheService'
import { isRedisReady } from '../utils/redis'
+import { SYSTEM_SETTINGS_DEFAULTS } from '~~/server/utils/system-settings-defaults'
// 过滤敏感字段,只返回公开配置
const publicFields = [
@@ -36,13 +37,11 @@ const publicFields = [
]
const filterPublicSettings = (data: any) => {
- if (!data) {
- return {}
- }
+ const merged: any = { ...SYSTEM_SETTINGS_DEFAULTS, ...(data || {}) }
const result: Record<string, any> = {}
for (const key of publicFields) {
- if (Object.prototype.hasOwnProperty.call(data, key)) {
- result[key] = data[key]
+ if (Object.prototype.hasOwnProperty.call(merged, key)) {
+ result[key] = merged[key]
}
}
return result
@@ -55,7 +54,7 @@ export default defineEventHandler(async (event) => {
const cachedSettings = await cacheService.getSystemSettings()
if (cachedSettings) {
console.log('[API] 系统设置缓存命中')
- return filterPublicSettings(cachedSettings)
+ return filterPublicSettings({ ...SYSTEM_SETTINGS_DEFAULTS, ...cachedSettings })
}
}
@@ -100,7 +99,7 @@ export default defineEventHandler(async (event) => {
settings = newSettings[0]
}
- const publicSettings = filterPublicSettings(settings)
+ const publicSettings = filterPublicSettings({ ...SYSTEM_SETTINGS_DEFAULTS, ...(settings || {}) })
// 将结果缓存到Redis(如果可用)- 永久缓存
if (settings && isRedisReady()) {
diff --git a/server/services/cacheService.ts b/server/services/cacheService.ts
index 7c91700..c28cbad 100644
--- a/server/services/cacheService.ts
+++ b/server/services/cacheService.ts
@@ -16,7 +16,7 @@ const CACHE_TTL = {
SONG_COUNT: 180, // 3分钟
USER_VOTES: 120, // 2分钟
EMPTY_RESULT: 60, // 1分钟(空结果缓存时间较短)
- SYSTEM_SETTINGS: 3600, // 60分钟
+ SYSTEM_SETTINGS: 900, // 15分钟(新功能上线后避免 Redis 缓存旧 DB 值导致主题/排期等配置失效)
RANDOM_OFFSET: 60, // 随机偏移量,防止缓存雪崩
LOCK_TIMEOUT: 30, // 分布式锁超时时间
REFRESH_AHEAD: 60 // 提前刷新时间(秒)
--
2.45.1.windows.1