// server/api/admin/system-config.put.ts - 更新系统配置(仅 superadmin 可访问) // // 流程:鉴权 → 二次校验 superadmin → 白名单校验 → upsert system_config → 失效缓存 → 返回 // 安全边界: // - handler 内独立校验 role === 'superadmin' // - openAiApiKey 为空字符串时跳过,不清空已有 key // - 更新成功后立即失效系统配置缓存,下次请求即生效 import { readBody, setResponseStatus } from "h3"; import { prisma } from "~~/server/utils/db"; import { createApiLogger, toSafeLogError } from "~~/server/utils/logging"; import { apiErr, apiOk } from "~~/server/utils/response"; import { invalidateSysConfigCache } from "~~/server/utils/sysConfig"; import type { ISystemConfigUpdateRequest } from "~~/shared/types/settings"; const isRecord = (v: unknown): v is Record => typeof v === "object" && v !== null && !Array.isArray(v); /** system_config 表 key 的常量映射 */ const SYS_KEY = { openAiApiKey: "openai_api_key", openAiApiBase: "openai_api_base", openAiModel: "openai_model", maxTokens: "max_tokens", temperature: "temperature", maxSseLineBytes: "openai_stream_max_sse_line_bytes", allowRegistration: "allow_registration" } as const; export default defineEventHandler(async (event) => { const logger = createApiLogger(event, "admin.system-config.put"); const role = (event.context.auth?.user as { role?: string } | undefined) ?.role; if (role !== "superadmin") { setResponseStatus(event, 403); return apiErr(403, "权限不足"); } const body = await readBody(event).catch(() => null); if (!isRecord(body)) { setResponseStatus(event, 400); return apiErr(400, "请求体必须是 JSON 对象"); } const req: ISystemConfigUpdateRequest = {}; if (typeof body.openAiApiBase === "string") { req.openAiApiBase = body.openAiApiBase.trim(); } if (typeof body.openAiApiKey === "string") { req.openAiApiKey = body.openAiApiKey; // 空字符串 = 跳过,见下方逻辑 } if (typeof body.openAiModel === "string") { req.openAiModel = body.openAiModel.trim(); } if (typeof body.maxTokens === "number") { req.maxTokens = body.maxTokens; } if (typeof body.temperature === "number") { req.temperature = body.temperature; } if (typeof body.maxSseLineBytes === "number") { req.maxSseLineBytes = body.maxSseLineBytes; } if (typeof body.allowRegistration === "boolean") { req.allowRegistration = body.allowRegistration; } // 构建需要写入的 KV 对(key 为空时跳过) type SysKv = { key: string; value: string }; const upserts: SysKv[] = []; if (req.openAiApiBase) { upserts.push({ key: SYS_KEY.openAiApiBase, value: req.openAiApiBase }); } // key 为空字符串时跳过,不清空已有 key if (req.openAiApiKey) { upserts.push({ key: SYS_KEY.openAiApiKey, value: req.openAiApiKey }); } if (req.openAiModel) { upserts.push({ key: SYS_KEY.openAiModel, value: req.openAiModel }); } if (req.maxTokens !== undefined) { upserts.push({ key: SYS_KEY.maxTokens, value: String(req.maxTokens) }); } if (req.temperature !== undefined) { upserts.push({ key: SYS_KEY.temperature, value: String(req.temperature) }); } if (req.maxSseLineBytes !== undefined) { upserts.push({ key: SYS_KEY.maxSseLineBytes, value: String(req.maxSseLineBytes) }); } if (req.allowRegistration !== undefined) { upserts.push({ key: SYS_KEY.allowRegistration, value: String(req.allowRegistration) }); } if (upserts.length === 0) { return apiOk(null); } try { await Promise.all( upserts.map((item) => prisma.systemConfig.upsert({ where: { key: item.key }, create: { key: item.key, value: item.value }, update: { value: item.value } }) ) ); // 立即失效缓存,确保下次请求读到最新配置 invalidateSysConfigCache(); logger.info("system_config_updated", { updatedKeys: upserts.map((u) => u.key) }); return apiOk(null); } catch (error) { logger.error("system_config_update_failed", { error: toSafeLogError(error) }); setResponseStatus(event, 500); return apiErr(500, "服务器内部错误"); } });