refactor(db): 统一数据库 schema — 软删除、命名规范、约束标准化
- 全表新增 deleted_at 列,统一软删除替代硬删除+archived_at - models.model_id 重命名为 external_id,消除语义混淆 - conversations.model_id 改为可空(模型为建议而非绑定) - messages 新增 updated_at,移除 CASCADE 改为 DAO 层级联 - 移除 DB 层 UNIQUE 约束,改为应用层检查(配合软删除) - 新增 helpers.ts(baseColumns + 构造层防御)、ESLint 规则、契约测试 - 迁移 0004 补全 CHECK 约束(providers.type/materials.status/messages.role) - DAO 层全面重写:级联软删除、应用层唯一、provider 删除保护 - 路由/前端/测试全量适配 externalId 重命名及类型变更
This commit is contained in:
@@ -13,7 +13,7 @@ import {
|
||||
updateConversation,
|
||||
updateConversationTimestamp,
|
||||
} from "../../db/conversations";
|
||||
import { getModelWithProvider } from "../../db/models";
|
||||
import { getModelWithProvider, listModels } from "../../db/models";
|
||||
import { createApiError, jsonResponse } from "../../helpers";
|
||||
import { validateIdParam } from "../../middleware";
|
||||
|
||||
@@ -79,13 +79,23 @@ export async function handleSendChat(req: Request, db: Database, mode: RuntimeMo
|
||||
|
||||
let model;
|
||||
try {
|
||||
const result = getModelWithProvider(db, conversation.modelId);
|
||||
let effectiveModelId = conversation.modelId;
|
||||
if (!effectiveModelId) {
|
||||
const fallback = listModels(db, { page: 1, pageSize: 1 });
|
||||
const firstModel = fallback.items[0];
|
||||
if (!firstModel) {
|
||||
return jsonResponse(createApiError("没有可用的模型,请先配置模型", 400), { mode, status: 400 });
|
||||
}
|
||||
effectiveModelId = firstModel.id;
|
||||
}
|
||||
|
||||
const result = getModelWithProvider(db, effectiveModelId);
|
||||
if ("error" in result) {
|
||||
return jsonResponse(createApiError(result.error, result.status), { mode, status: result.status });
|
||||
}
|
||||
|
||||
const registry = buildProviderRegistry(db);
|
||||
model = registry.languageModel(`${result.provider.id}:${result.model.modelId}`);
|
||||
model = registry.languageModel(`${result.provider.id}:${result.model.externalId}`);
|
||||
} catch (e: unknown) {
|
||||
const msg = e instanceof Error ? e.message : String(e);
|
||||
return jsonResponse(createApiError(`模型初始化失败:${msg}`, 500), { mode, status: 500 });
|
||||
|
||||
@@ -25,8 +25,8 @@ export async function handleCreateModel(
|
||||
return jsonResponse(createApiError("name is required", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
if (!body.modelId || typeof body.modelId !== "string") {
|
||||
return jsonResponse(createApiError("modelId is required", 400), { mode, status: 400 });
|
||||
if (!body.externalId || typeof body.externalId !== "string") {
|
||||
return jsonResponse(createApiError("externalId is required", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
if (!body.providerId || typeof body.providerId !== "string") {
|
||||
|
||||
@@ -25,8 +25,8 @@ export async function handleTestModelConfig(
|
||||
return jsonResponse(createApiError("providerId is required", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
if (!body.modelId || typeof body.modelId !== "string") {
|
||||
return jsonResponse(createApiError("modelId is required", 400), { mode, status: 400 });
|
||||
if (!body.externalId || typeof body.externalId !== "string") {
|
||||
return jsonResponse(createApiError("externalId is required", 400), { mode, status: 400 });
|
||||
}
|
||||
|
||||
const providerResult = getProvider(db, body.providerId);
|
||||
@@ -41,7 +41,7 @@ export async function handleTestModelConfig(
|
||||
{
|
||||
apiKey: providerResult.provider.apiKey,
|
||||
baseUrl: providerResult.provider.baseUrl,
|
||||
modelId: body.modelId,
|
||||
modelId: body.externalId,
|
||||
name: providerResult.provider.name,
|
||||
type: providerResult.provider.type,
|
||||
},
|
||||
@@ -50,7 +50,7 @@ export async function handleTestModelConfig(
|
||||
|
||||
if (!testResult.ok) {
|
||||
logger.warn(
|
||||
{ message: testResult.message, modelId: body.modelId, providerId: body.providerId },
|
||||
{ externalId: body.externalId, message: testResult.message, providerId: body.providerId },
|
||||
"模型连接测试失败",
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user