Files
OCS_service/server/api/admin/system-config.put.ts
T
2026-05-23 23:48:30 +08:00

134 lines
4.2 KiB
TypeScript

// 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<string, unknown> =>
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<unknown>(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, "服务器内部错误");
}
});