feat: 重构配置管理,使用数据库替代环境变量,优化系统配置读取

This commit is contained in:
2026-05-23 15:37:58 +08:00
parent ea95886cfc
commit 92b063c4c4
10 changed files with 249 additions and 129 deletions
+20 -14
View File
@@ -1,8 +1,6 @@
// server/utils/openai.ts - OpenAI Chat Completions 流式调用工具
import { serverEnv } from "~~/server/utils/env";
import { getSysConfig } from "~~/server/utils/sysConfig";
/** 旧项目的 `OPENAI_API_BASE` 语义是完整 base,例如 `https://api.openai.com/v1` */
const CHAT_COMPLETIONS_URL = `${serverEnv.openAiApiBase}/chat/completions`;
/** 复用编码器计算 SSE data 字节长度,避免循环里频繁创建对象 */
const textEncoder = new TextEncoder();
@@ -56,21 +54,25 @@ export const askAnswerStream = async ({
prompt: string;
systemPrompt: string;
}): Promise<AskAnswerStreamResult> => {
if (!serverEnv.openAiApiKey) {
const cfg = await getSysConfig();
if (!cfg.openAiApiKey) {
throw new Error("OPENAI_API_KEY is not set.");
}
const chatCompletionsUrl = `${cfg.openAiApiBase}/chat/completions`;
// 这里不要记录 request body:其中包含完整题目和可能的选项文本
const response = await fetch(CHAT_COMPLETIONS_URL, {
const response = await fetch(chatCompletionsUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${serverEnv.openAiApiKey}`,
Authorization: `Bearer ${cfg.openAiApiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: serverEnv.openAiModel,
temperature: serverEnv.temperature,
max_tokens: serverEnv.maxTokens,
model: cfg.openAiModel,
temperature: cfg.temperature,
max_tokens: cfg.maxTokens,
stream: true,
messages: [
{
@@ -94,7 +96,10 @@ export const askAnswerStream = async ({
}
// 将 SSE delta 读完后再返回给 API handler,由 handler 统一做答案清洗和缓存
const upstreamResponse = await readChatCompletionStream(response.body);
const upstreamResponse = await readChatCompletionStream(
response.body,
cfg.maxSseLineBytes
);
return {
answer: upstreamResponse.content.trim(),
@@ -112,7 +117,8 @@ export const askAnswerStream = async ({
* 网络 chunk 不一定按行对齐,所以用 buffer 保存尚未拼完整的一行
*/
export const readChatCompletionStream = async (
stream: ReadableStream<Uint8Array>
stream: ReadableStream<Uint8Array>,
maxSseLineBytes: number
): Promise<ChatCompletionStreamResult> => {
const reader = stream.getReader();
const decoder = new TextDecoder();
@@ -146,14 +152,14 @@ export const readChatCompletionStream = async (
// stream: true 可以正确处理跨 chunk 的多字节字符,例如中文
buffer += decoder.decode(value, { stream: true });
assertSseBufferSize(buffer, serverEnv.maxSseLineBytes);
assertSseBufferSize(buffer, maxSseLineBytes);
// 只处理已经遇到换行的完整 SSE 行,最后一段留到下次 chunk 再拼
const lines = buffer.split(/\r?\n/u);
buffer = lines.pop() ?? "";
for (const line of lines) {
const chunk = parseSseDataLine(line, serverEnv.maxSseLineBytes);
const chunk = parseSseDataLine(line, maxSseLineBytes);
if (chunk) collectChunk(chunk);
}
}
@@ -161,7 +167,7 @@ export const readChatCompletionStream = async (
// 处理流结束时仍留在 buffer 中的最后一行
const finalText = buffer + decoder.decode();
for (const line of finalText.split(/\r?\n/u)) {
const chunk = parseSseDataLine(line, serverEnv.maxSseLineBytes);
const chunk = parseSseDataLine(line, maxSseLineBytes);
if (chunk) collectChunk(chunk);
}