feat: 大量优化和bug修复
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// server/utils/sysConfig.ts - 从数据库读取系统配置,带 60 秒 TTL 内存缓存
|
||||
// server/utils/sysConfig.ts - 从数据库读取系统配置,带 60 秒 TTL 内存缓存;支持按用户自定义配置覆盖
|
||||
// 替代原 env.ts 中的 OpenAI 配置项,配置以 DB 为准,管理员可通过后台修改
|
||||
import { prisma } from "~~/server/utils/db";
|
||||
import { normalizeUserConfigValue } from "~~/server/utils/userConfig";
|
||||
|
||||
/** 系统配置字段,对应 system_config 表中的各 key */
|
||||
export interface SysConfig {
|
||||
@@ -32,7 +33,7 @@ const CONFIG_KEYS = {
|
||||
const DEFAULTS: SysConfig = {
|
||||
openAiApiKey: "",
|
||||
openAiApiBase: "https://api.openai.com/v1",
|
||||
openAiModel: "gpt-3.5-turbo",
|
||||
openAiModel: "gpt-5.2",
|
||||
maxTokens: 500,
|
||||
temperature: 0.7,
|
||||
maxSseLineBytes: 1_048_576
|
||||
@@ -94,3 +95,70 @@ export const invalidateSysConfigCache = () => {
|
||||
_cachedConfig = null;
|
||||
_cacheExpiresAt = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户生效的 API 配置
|
||||
*
|
||||
* 安全规则(保证系统 key 不流向用户控制的 endpoint):
|
||||
* - useCustomConfig=true 且 customApiBase + customApiKey 都非空
|
||||
* → base 和 key 用用户自定义值;model/max_tokens/temperature 有值则各自覆盖系统值
|
||||
* - 上述条件任一不满足
|
||||
* → base 和 key 完全使用系统配置(系统 key 不进用户 base)
|
||||
* - useCustomConfig=false 或未设置
|
||||
* → 完全使用系统配置,用户覆盖项全部忽略
|
||||
*/
|
||||
export const getUserEffectiveApiConfig = async (
|
||||
userId: string
|
||||
): Promise<SysConfig> => {
|
||||
const sysCfg = await getSysConfig();
|
||||
|
||||
const row = await prisma.userConfig.findUnique({
|
||||
where: { userId },
|
||||
select: { value: true }
|
||||
});
|
||||
const userConfig = normalizeUserConfigValue(row?.value);
|
||||
const useCustom = userConfig.useCustomConfig;
|
||||
|
||||
if (!useCustom) {
|
||||
return sysCfg;
|
||||
}
|
||||
|
||||
const customBase = userConfig.customApiBase ?? "";
|
||||
const customKey = userConfig.customApiKey?.trim() ?? "";
|
||||
|
||||
// base 和 key 必须同时有值,才允许覆盖;否则继续使用系统的 base+key
|
||||
// 这样可以保证系统 key 绝不发往用户控制的 endpoint
|
||||
const useCustomEndpoint = customBase.length > 0 && customKey.length > 0;
|
||||
|
||||
const effectiveBase = useCustomEndpoint
|
||||
? normalizeBaseUrl(customBase)
|
||||
: sysCfg.openAiApiBase;
|
||||
const effectiveKey = useCustomEndpoint ? customKey : sysCfg.openAiApiKey;
|
||||
|
||||
// model / max_tokens / temperature 在自定义配置开启时可以各自独立覆盖
|
||||
const customModel = userConfig.customModel ?? "";
|
||||
|
||||
return {
|
||||
openAiApiBase: effectiveBase,
|
||||
openAiApiKey: effectiveKey,
|
||||
openAiModel: customModel.length > 0 ? customModel : sysCfg.openAiModel,
|
||||
maxTokens: userConfig.customMaxTokens ?? sysCfg.maxTokens,
|
||||
temperature: userConfig.customTemperature ?? sysCfg.temperature,
|
||||
maxSseLineBytes: sysCfg.maxSseLineBytes
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 遮蔽 API Key,只留前 4 后 4 位,用于前端展示
|
||||
*
|
||||
* 长度 > 8 → 前4 + "..." + 后4(如 `sk-a...xxxx`)
|
||||
* 长度 1~8 → 全部替换为 `****`
|
||||
* 空 → null
|
||||
*/
|
||||
export const maskApiKey = (key: string): string | null => {
|
||||
if (!key) return null;
|
||||
if (key.length > 8) {
|
||||
return `${key.slice(0, 4)}...${key.slice(-4)}`;
|
||||
}
|
||||
return "****";
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user