# 后端开发 本文档说明 alfred 后端的 API、配置加载、日志、版本管理和后端测试开发约定。 适用场景:修改 src/server/、src/shared/api.ts、后端测试、配置契约、API 响应或日志模块。 ## 库使用优先级 | 优先级 | 来源 | 典型用途 | | ------ | ------------ | ---------------------------------------------------- | | 1 | Bun 内置 API | Bun.serve、Bun.file、Bun.YAML、Bun.spawn、bun:sqlite | | 2 | es-toolkit | 类型判断、深度比较、并发控制 | | 3 | 标准 Web API | Headers、fetch、AbortController | | 4 | 主流三方库 | pino、@sinclair/typebox、ajv、drizzle-orm | | 5 | 自行实现 | 仅在以上都无法满足时 | 新增依赖前必须先检查上述每一层是否已有可用方案。 ## API 路由开发 路由文件位于 src/server/routes/,每个端点一个文件。路由通过 server.ts 的 Bun.serve({ routes }) 声明式注册。 新增路由步骤: 1. 在 src/server/routes/ 下创建 .ts 2. 实现 handler 函数并 export 3. 在 server.ts 的 routes 对象中注册路径和 method handler 4. 在 tests/server/ 中添加对应测试 ## 共享工具 helpers.ts 提供跨路由共用的响应工具: - createApiError(error, status) — 构造 API 错误体 - createHeaders(mode, init) — 创建响应 Headers - jsonResponse(body, options) — JSON 响应构造 middleware.ts 提供 API 参数校验函数: - validateIdParam(idStr, mode) — 校验 ID 参数格式 - validatePagination(pageParam, pageSizeParam, mode) — 校验分页参数 - validateTimeRange(from, to, mode) — 校验时间范围参数 ## 数据库 项目使用 SQLite 作为存储后端,通过 bun:sqlite + Drizzle ORM 实现类型安全的数据访问。 ### schema 定义 `src/server/db/schema.ts` 使用 Drizzle ORM 定义表结构,列名使用 snake_case,TypeScript 类型使用 camelCase,Drizzle schema 负责映射。 ### 数据库连接 `src/server/db/connection.ts` 的 `createDatabase(dataDir, logger)` 打开 `/alfred.db`,设置 PRAGMA(foreign_keys=ON、journal_mode=WAL、busy_timeout=5000)。 ### migration 机制 - 开发期:使用 `drizzle-kit generate` 从 TS schema 生成 SQL migration 文件到 `drizzle/` 目录 - 生产期:构建时将 `drizzle/*.sql` 嵌入可执行文件,启动时自动应用 pending migrations - 每次 migration 前自动备份现有 DB 到 `/backups/alfred-.db` - migration 在事务中执行,失败则回滚并停止启动 ### 数据访问 `src/server/db/projects.ts` 提供项目数据访问函数,`src/server/db/providers.ts` 提供供应商数据访问函数,`src/server/db/models.ts` 提供模型数据访问函数,`src/server/db/conversations.ts` 提供会话和消息数据访问函数。输入输出使用 `src/shared/api.ts` 的类型。函数内部使用 Drizzle query builder 包装 `bun:sqlite` Database。 ## AI 服务层 `src/server/ai/` 提供 AI Provider Registry 构建与连接测试能力。 ### 类型定义 `src/server/ai/types.ts` 定义 AI 配置类型: - `AIProviderConfig` — 供应商配置(name、type、baseUrl、apiKey) - `AIModelConfig` — 模型配置(providerId、modelId、capabilities) - `AIRegistryConfig` — Registry 构建配置(providers、models),供后续 AI 调用层组合使用 ### Registry 构建 `src/server/ai/registry.ts` 提供: - `buildProviderRegistry(db)` — 从 DB 查询所有供应商,构建 Vercel AI SDK Provider Registry - `testProviderConnection(config)` — 先测试 Base URL 可达性,再请求 `/models` 验证 API Key 和模型列表接口 每次 AI 调用时从 DB 查询 providers,构建 registry 后通过 `registry.languageModel('providerId:modelId')` 获取模型实例。不使用缓存层。模型是否存在以及业务能力标签由调用方基于 models 表先行校验,registry 只负责将 providerId/modelId 映射到 AI SDK 模型实例。 ### Agent 流式调用 `src/server/ai/agent-stream.ts` 提供 `agentStream(options)` 函数,封装 Vercel AI SDK `streamText` 调用。接收数据库实例、消息数组和模型 DB ID,从 DB 查询模型与供应商信息后构建 Provider Registry,使用 `:` 作为 provider 和 modelId 的分隔符。默认使用 `stepCountIs(1)` 限制单步调用。返回 `StreamTextResult`,路由层通过 `result.toUIMessageStreamResponse()` 转为 SSE 响应。 ### 供应商连通性测试 供应商连通性测试返回 `{ providerTestResponse: { ok, message } }`,前端根据 `ok` 展示成功或失败提示。 - `POST /api/providers/test` — 使用表单中尚未保存的供应商配置测试连接 - `POST /api/models/test` — 使用模型关联供应商配置和 modelId 测试模型连接 测试连接不会写入数据库,也不会阻止保存。Base URL 不可达或 API Key 无效返回 `ok: false`;Base URL 可达但 `/models` 不支持、非标准或返回非鉴权错误时返回 `ok: true` 并在 `message` 中提示用户可检查 URL 或忽略提醒。 ### 支持的供应商类型 | type | AI SDK factory | | ------------------- | --------------------------------------------------- | | `openai` | `createOpenAI({ apiKey, baseURL })` | | `anthropic` | `createAnthropic({ apiKey, baseURL })` | | `openai-compatible` | `createOpenAICompatible({ name, apiKey, baseURL })` | ## 聊天 API 聊天 API 按项目维度组织会话和消息: | 方法 | 路径 | 说明 | | ------ | ----------------------------------------------- | ------------------ | | GET | `/api/projects/:id/conversations` | 列出项目下所有会话 | | POST | `/api/projects/:id/conversations` | 创建新会话 | | GET | `/api/projects/:id/conversations/:cid` | 获取会话详情 | | DELETE | `/api/projects/:id/conversations/:cid` | 删除会话及其消息 | | GET | `/api/projects/:id/conversations/:cid/messages` | 获取会话消息列表 | | POST | `/api/projects/:id/chat` | 发送消息并流式回复 | 聊天路由处理器位于 `src/server/routes/chat/`,遵循统一的 handler 模式。`send.ts` 处理发送消息:验证会话归属后保存用户消息到 DB,调用 `agentStream` 获取流式响应,返回 SSE UI 消息流,流结束后后台保存 AI 回复到 DB。 ## 类型规范 - 共享类型以 src/shared/api.ts 为唯一源头 - 应用常量以 src/shared/app.ts 为唯一源头 - 版本号以 package.json.version 为唯一源头 - 前端不得 import src/server/ 下的任何文件 - 严格联合类型优先于宽类型 ## 配置契约 配置加载流程固定为:unknown -> AuthoringConfig -> NormalizedConfig -> ValidatedConfig -> ServerConfig。 Ajv 保持严格拒绝模式:allErrors: true、不启用类型强制转换、不注入默认值、不自动删除未知字段。 新增或修改配置字段时必须同步更新 TypeBox schema fragments、config.schema.json、测试和对应用户文档。 ## 日志模块 后端运行时代码统一通过 Logger 接口输出日志,禁止直接使用 console.\*。 | 实现 | 用途 | | --------------------- | ------------------------ | | PinoLoggerWrapper | 生产运行时 | | ConsoleFallbackLogger | 配置加载失败前的降级日志 | | NoopLogger | 静默丢弃日志 | | MemoryLogger | 测试替身 | 敏感信息会自动 redact authorization、cookie、password 等字段。 ## 版本管理 项目使用 package.json.version 作为版本号唯一来源。 版本获取方式: - 开发模式:src/server/version.ts 运行时从 package.json 读取 - 生产模式:scripts/build.ts 在构建时将版本号烘焙为字面量注入 版本升迁命令: ```bash bun run version:patch # 升迁 patch 版本 bun run version:minor # 升迁 minor 版本 bun run version:major # 升迁 major 版本 bun run version:set # 显式设置版本号 ``` ## 后端测试 | 变更类型 | 测试重点 | | ------------------ | --------------------------------- | | API 路由 | tests/server/app.test.ts 集成行为 | | 配置 schema | schema 导出、合法/非法配置 | | helpers/middleware | 单元测试 | 后端测试约定: - API 路由集成测试必须通过真实 `startServer` 覆盖路由注册、HTTP method、fallback、响应 header 和核心错误路径。 - SQLite 相关测试必须复用 `tests/helpers.ts` 中的测试数据库 helper,不要在测试文件内分散实现临时目录清理或直接裸用 `rmSync(dir, { recursive: true })`。 - DAO 和路由边界测试应优先使用真实 migration 初始化测试库,只有 migration 执行器单测可以使用最小 fake migration。 - logger/bootstrap 中预期的 fallback 输出必须在测试中捕获并断言,正常通过的测试不应污染 stdout/stderr。 ## 更新触发条件 修改后端 API、共享类型、配置契约、日志模块、版本管理或后端测试规范时,必须更新本文档。