Files
Alfred/docs/development/backend.md

125 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端开发
开发规范见 [开发规范文档](README.md)。
## 共享工具
`src/server/helpers/`:
- `response.ts``createApiError(error, status)``createHeaders(mode, init)``createMetaResponse(version)``formatDuration(ms)``jsonResponse(body, options)`
- `url.ts``parseIdFromUrl(url)`
- `list-params.ts``parseListParams(url, mode, options?)` — 统一校验分页/排序参数,替代 validatePagination
- `pagination.ts``paginateQuery()` — Drizzle 分页查询封装
`src/server/middleware/`:
- `validate.ts``validateIdParam(idStr, mode)` — 校验 ID 格式(字母数字 + `_-``validatePagination(pageParam, pageSizeParam, mode)` — page≥1, pageSize≤200`validateTimeRange(from, to, mode)`
- `error-handler.ts``AppError` — 业务异常类(含 statusCode`withErrorHandler(fn, mode, logger?)` — 包裹 handler 捕获异常
## 数据库
SQLite + bun:sqlite + Drizzle ORM。
- `src/server/db/schema.ts`Drizzle 表结构,列名 snake_caseTS 类型 camelCase。所有业务表通过 `helpers.ts``baseColumns` 获取 id/created_at/updated_at/deleted_at。
- `src/server/db/helpers.ts``baseColumns` 常量id、createdAt、updatedAt、deletedAt+ Drizzle 构建器再导出。`src/server/db/` 内禁止直接从 `drizzle-orm/sqlite-core` 导入 `sqliteTable`ESLint 强制)。
- `src/server/db/connection.ts``createDatabase(dataDir, logger)` 打开 `alfred.db`PRAGMAforeign_keys=ON、journal_mode=WAL、busy_timeout=5000。`wrap(db)` 转 Drizzle 实例(`DrizzleDB` 类型)。工具函数:`timestamp()``notDeleted(table)``softDeleteRecord(db, table, id)``paginateQuery()`(支持 `softDelete` 参数自动过滤已删除行)。
- Migration开发期 `drizzle-kit generate` 产出到 `drizzle/`;生产期嵌入可执行文件,启动时自动应用。备份到 `<dataDir>/backups/`,事务中执行(迁移期间临时关闭外键检查),失败回滚。
### 软删除
所有业务表projects、providers、models、conversations、materials、messages使用 `deleted_at` 列实现软删除,不暴露给 API 层。DAO 查询通过 `notDeleted(table)``paginateQuery({ softDelete })` 自动过滤已删除行。唯一性校验在应用层完成(同名 + `deleted_at IS NULL`),无数据库级 UNIQUE 约束。级联软删除:删除项目 → 级联软删除会话(→ 消息)+ 素材;删除会话 → 级联软删除消息;删除供应商 → 需无未删除模型。
### 数据访问函数
| 文件 | 函数 |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `projects.ts` | createProject、getProject、listProjects、updateProject、deleteProject、archiveProject、restoreProject |
| `providers.ts` | createProvider、getProvider、listProviders、listProviderOptions、updateProvider、deleteProvider |
| `models.ts` | createModel、getModel、listModels、getModelWithProvider、getModelsByProviderId、updateModel、deleteModel |
| `conversations.ts` | createConversation、getConversation、listConversations、updateConversation、updateConversationTimestamp、deleteConversation、createMessage、createMessages、listMessages |
| `materials.ts` | createMaterial、getMaterial、listMaterials、deleteMaterial、approveMaterial、discardMaterial、retryMaterial |
输入输出类型来自 `src/shared/api.ts`
## AI 服务层
- `src/server/ai/types.ts``AIProviderConfig`name、type、baseUrl、apiKey`AIModelConfig`providerId、modelId、capabilities。注AI 层 `modelId` 对应 DB 层 `Model.externalId`
- `src/server/ai/registry.ts`
- `buildProviderRegistry(db)` — 从 DB 查询供应商构建 AI SDK Provider Registry每次调用重建不缓存。通过 `registry.languageModel('providerId:modelId')` 获取模型实例。
- `testProviderConnection(config, logger)` — 测试 Base URL 可达性 + `/models` 接口
- `testModelConnection(config, logger)` — 测试模型连通性(需传入含 modelId 的合并配置)
- `src/server/ai/agents/alfred-agent.ts``createAlfredAgent(model)` — ToolLoopAgent + `stepCountIs(20)` + `getCurrentTime` 工具。
- `src/server/ai/tools/`AI 工具定义。
### 供应商类型
| type | AI SDK factory |
| ------------------- | --------------------------------------------------- |
| `openai` | `createOpenAI({ apiKey, baseURL })` |
| `anthropic` | `createAnthropic({ apiKey, baseURL })` |
| `openai-compatible` | `createOpenAICompatible({ name, apiKey, baseURL })` |
### 连通性测试
- `POST /api/providers/test` — 用未保存配置测试,不写入 DB不阻止保存。Base URL 不可达或 API Key 无效返回 `ok: false``/models` 不支持返回 `ok: true` + 提示。
- `POST /api/models/test` — 用模型关联供应商 + externalId 测试。
## 素材 API
| 方法 | 路径 | 说明 |
| ------ | ------------------------------------------ | ------------------------------- |
| GET | `/api/projects/:id/materials` | 列出项目下素材(分页+状态筛选) |
| POST | `/api/projects/:id/materials` | 创建素材 |
| GET | `/api/projects/:id/materials/:mid` | 获取素材详情 |
| DELETE | `/api/projects/:id/materials/:mid` | 删除素材(软删除) |
| POST | `/api/projects/:id/materials/:mid/approve` | 审核通过(需 review 状态) |
| POST | `/api/projects/:id/materials/:mid/discard` | 审核放弃(需 review 状态) |
| POST | `/api/projects/:id/materials/:mid/retry` | 重试处理(需 failed 状态) |
素材状态流转:`pending → processing → review → approved/discarded`,失败分支 `processing → failed`,用户重试 `failed → pending`。共 6 种状态:`pending``processing``review``approved``discarded``failed`
素材类型:`general`(通用)和 `meeting`(会议),创建时可选,默认 `general`
校验description 必填非空associatedDate 必填 YYYY-MM-DD项目须存在且 active 且未删除,素材归属校验不匹配返回 403。processing 状态禁止删除409。approve/discard 仅限 review 状态409retry 仅限 failed 状态409
## 素材处理引擎
`src/server/processing/`
- `processor.ts``MaterialProcessor` 类 — 后台定时扫描 pending 素材,按 FIFO 顺序处理(每 5 秒扫描一次),每次处理最多重试 3 次,成功后 status=review 并设置 processedContent全部失败后 status=failed。启动时自动恢复所有 processing 状态的素材为 pending`recoverStuckMaterials`)。
- `templates/`:处理模板目录 — `general.ts`(通用模板)和 `meeting.ts`(会议模板),当前为占位实现(原样回显 description`index.ts` 导出 `ProcessingTemplate` 类型和 `getTemplate(type)` 映射函数。
- `index.ts``startMaterialProcessor(db, logger)` — 工厂函数,创建并启动处理器实例。
处理器在 `bootstrap.ts` 中启动shutdown 时先停止处理器再关闭数据库。
## 聊天 API
| 方法 | 路径 | 说明 |
| ------ | ----------------------------------------------- | ------------------ |
| GET | `/api/projects/:id/conversations` | 列出项目下所有会话 |
| POST | `/api/projects/:id/conversations` | 创建新会话 |
| GET | `/api/projects/:id/conversations/:cid` | 获取会话详情 |
| PATCH | `/api/projects/:id/conversations/:cid` | 更新会话 |
| DELETE | `/api/projects/:id/conversations/:cid` | 删除会话及消息 |
| GET | `/api/projects/:id/conversations/:cid/messages` | 获取消息列表 |
| POST | `/api/projects/:id/chat` | 发送消息SSE 回复 |
`send.ts`:验证会话归属 → 保存用户消息 → `createAlfredAgent``createAgentUIStreamResponse``onFinish` 持久化 AI 回复。
## 日志
| 实现 | 用途 |
| --------------------- | -------------- |
| PinoLoggerWrapper | 生产运行时 |
| ConsoleFallbackLogger | 配置加载前降级 |
| NoopLogger | 静默丢弃 |
| MemoryLogger | 测试替身 |
## 版本管理
唯一来源:`package.json`。开发模式从 package.json 运行时读取;生产模式构建时烘焙为字面量。
## 更新触发条件
修改后端模块 API、共享工具、数据库 schema、AI 服务层、聊天 API 或列表查询参数解析时,必须更新本文档。管理页面 CRUD 通用模式的详细约定见 [crud.md](crud.md)。