# 数据库设计 > 权威来源是 `backend/app/models/`:本文是它的可读镜像,两者冲突时**以模型为准并回改本文**。 > 结构变更流程见 [quickstart.md §9](quickstart.md#9-数据库结构与迁移)。 - 引擎:MySQL 8.0 / InnoDB / `utf8mb4` / `utf8mb4_unicode_ci` - 表数量:**18** - 时间列:`created_at` / `updated_at` 由各模型的 `default=now` / `onupdate=now` 维护(DATETIME,无时区,容器时区固定 `Asia/Shanghai`) - 主键:除 `llm_model_config.config_id` 外,其余表主键均为自增 `id` - ⚠️ **文档正文不在数据库里**:Markdown 正文存在文件系统(`STORAGE_ROOT/projects//…`),数据库只存权限、元数据与索引(见 [sdd ADR-0001](sdd/architecture/decisions/ADR-0001-hybrid-storage.md)、[ADR-0002](sdd/architecture/decisions/ADR-0002-uuid-storage-key.md)) ## 表清单 | 分组 | 表 | 用途 | 主要写入方 | | --- | --- | --- | --- | | 身份权限 | `users` | 账号、状态、超管标记、登录痕迹 | 注册、用户管理、登录 | | 身份权限 | `roles` | 角色(super_admin / admin / user + 自定义) | 角色管理、`init_db.py` 种子 | | 身份权限 | `user_roles` | 用户 ↔ 角色 | 用户管理 | | 身份权限 | `system_menus` | 菜单 **与** 按钮级权限点(`menu_type` 区分) | `init_db.py` 种子、权限管理 | | 身份权限 | `role_menus` | 角色 ↔ 菜单/权限点 | 权限管理 | | 项目文档 | `projects` | 项目主表,`storage_key` 是磁盘目录名 | 项目管理 | | 项目文档 | `project_members` | 项目成员与项目内角色 | 成员管理 | | 项目文档 | `document_meta` | 文档标题/标签/字数/浏览与编辑痕迹(可选表,正文仍在文件) | 保存文件、浏览统计 | | 项目文档 | `project_git_repos` | 项目绑定的 Git 仓库(含令牌) | Git 同步设置 | | 项目文档 | `share_links` | 项目/文件分享码与访问密码 | 分享管理 | | AI 知识库 | `llm_model_config` | Chat / Embedding 模型配置 | 模型配置 | | AI 知识库 | `chat_session` | 对话会话(按项目 + 用户 + 模型) | 对话 | | AI 知识库 | `chat_message` | 消息、状态、耗时、思考过程、引用文件 | 对话 | | AI 知识库 | `document_vector` | 文档分块 → 向量库映射(ZVec) | 向量化 | | AI 知识库 | `project_vectorization_task` | 全量/增量向量化任务与进度 | 向量化 | | 通知审计 | `notifications` | 站内通知 | 通知服务 | | 通知审计 | `operation_logs` | 操作审计(谁、何时、对什么、结果) | `log_service`(各 API 埋点) | | 通知审计 | `mcp_bots` | MCP Bot 的 `bot_id/secret` 凭证 | MCP 凭证管理 | ## 关系(逻辑外键) 数据库只在 `notifications.user_id` 与 `project_git_repos.project_id` 上声明了真正的 FOREIGN KEY,其余是**逻辑外键**(不建约束,便于分库与批量清理): ``` users 1─N user_roles N─1 roles 1─N role_menus N─1 system_menus users 1─N projects(owner_id) 1─N project_members N─1 users projects 1─N document_meta / share_links / project_git_repos / document_vector / project_vectorization_task / chat_session chat_session 1─N chat_message projects 1─N chat_session(同时 chat_session.user_id → users) ``` 删除项目时由服务层负责级联清理文件目录与相关索引行;不要指望数据库级联。 ## 初始化与迁移 | 阶段 | 代码 | 行为 | | --- | --- | --- | | 建表 | `backend/scripts/init_db.py` → `Base.metadata.create_all` | 只创建缺失表;表结构来自 `app/models/`。**模型必须在 `app/models/__init__.py` 中注册**,否则该表不会出现在新库里 | | 种子 | 同上 | 角色(super_admin/admin/user)、系统菜单与权限点、管理员账号;按 id 与 `(parent_id, menu_name)` 去重,幂等增量 | | 补列 | `app/core/migrations.py::migrate_schema()` | 幂等 `ALTER TABLE ADD COLUMN`(先查 `information_schema`)。当前覆盖:`chat_message.status/duration_ms/thinking_log`、`project_git_repos.sync_path` | | 未使用 | Alembic | 依赖里有 `alembic`,但项目未启用版本化迁移脚本 | Docker 部署时后端容器启动命令是 `python scripts/init_db.py && uvicorn main:app …`,因此**新环境无需手工执行 SQL**。 ## 已知数据差异与运维建议 1. **`project_members.role` 大小写混杂**:历史数据(早期 `init_database.sql` 建的库)多为 `ADMIN/EDITOR/VIEWER`,新写入是 `admin/editor/viewer`。读取路径统一经过 `app/services/project_service.normalize_project_role()` 归一化,因此行为正确;如需彻底清洗,可执行: ```sql UPDATE project_members SET role = LOWER(role) WHERE BINARY role <> LOWER(role); ``` 2. **`share_links.access_pass` / `project_git_repos.token` 为明文存储**:属已知技术债(见发布报告 P2)。上线前应限制库账号的远程访问并纳入备份加密范围。 3. **`document_vector` 无数据库唯一约束**:靠 `idx_project_file_chunk(project_id, file_path, chunk_index)` 普通索引查询,重复向量化由服务层用 `content_hash` 判定,不在库里去重。 4. **`llm_model_config.api_key` 明文**:同上,接口返回时会脱敏,但库里是原文。 5. **备份**:文件系统(`STORAGE_PATH`)与数据库必须一起备份,缺一个都无法还原,命令见 [deploy/README.md](deploy/README.md)。 ```sql -- 常用排查 SELECT id, name, storage_key, owner_id, is_public FROM projects WHERE id = ?; -- storage_key 即磁盘目录名 SELECT status, COUNT(*) FROM document_vector WHERE project_id = ? GROUP BY status; SELECT task_id, task_type, status, total, processed, failed FROM project_vectorization_task WHERE project_id = ? ORDER BY created_at DESC LIMIT 5; SELECT role, COUNT(*) FROM project_members WHERE project_id = ? GROUP BY role; ``` --- ## 身份与权限 ### `users` — 用户 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 用户ID | | `username` | VARCHAR(50) | UNIQUE · NOT NULL | 用户名 | | `password_hash` | VARCHAR(255) | NOT NULL | 密码哈希 | | `nickname` | VARCHAR(50) | — | 昵称 | | `email` | VARCHAR(100) | — | 邮箱 | | `phone` | VARCHAR(20) | — | 手机号 | | `avatar` | VARCHAR(255) | — | 头像URL | | `status` | SMALLINT | 默认 `1` | 状态:0-禁用 1-启用 | | `is_superuser` | SMALLINT | 默认 `0` | 是否超级管理员:0-否 1-是 | | `last_login_at` | DATETIME | — | 最后登录时间 | | `last_login_ip` | VARCHAR(50) | — | 最后登录IP | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_users_email`(email);`ix_users_status`(status);`ix_users_username`(username) UNIQUE ### `roles` — 角色 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 角色ID | | `role_name` | VARCHAR(50) | UNIQUE · NOT NULL | 角色名称 | | `role_code` | VARCHAR(50) | UNIQUE · NOT NULL | 角色编码 | | `description` | VARCHAR(255) | — | 角色描述 | | `status` | SMALLINT | 默认 `1` | 状态:0-禁用 1-启用 | | `is_system` | SMALLINT | 默认 `0` | 是否系统角色:0-否 1-是 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_roles_role_code`(role_code) UNIQUE;`ix_roles_status`(status) ### `user_roles` — 用户-角色关联 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 关联ID | | `user_id` | BIGINT | NOT NULL | 用户ID | | `role_id` | BIGINT | NOT NULL | 角色ID | | `created_at` | DATETIME | — | 创建时间 | 索引:`ix_user_roles_role_id`(role_id);`ix_user_roles_user_id`(user_id) ### `system_menus` — 系统菜单/权限点 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 菜单ID | | `parent_id` | BIGINT | 默认 `0` | 父菜单ID(0表示根菜单) | | `menu_name` | VARCHAR(50) | NOT NULL | 菜单名称 | | `menu_code` | VARCHAR(50) | UNIQUE · NOT NULL | 菜单编码 | | `menu_type` | SMALLINT | NOT NULL | 菜单类型:1-目录 2-菜单 3-按钮/权限点 | | `path` | VARCHAR(255) | — | 路由路径 | | `component` | VARCHAR(255) | — | 组件路径 | | `icon` | VARCHAR(100) | — | 图标 | | `sort_order` | INTEGER | 默认 `0` | 排序号 | | `visible` | SMALLINT | 默认 `1` | 是否可见:0-隐藏 1-显示 | | `status` | SMALLINT | 默认 `1` | 状态:0-禁用 1-启用 | | `permission` | VARCHAR(100) | — | 权限字符串 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_system_menus_menu_code`(menu_code) UNIQUE;`ix_system_menus_status`(status) ### `role_menus` — 角色-菜单授权 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 关联ID | | `role_id` | BIGINT | NOT NULL | 角色ID | | `menu_id` | BIGINT | NOT NULL | 菜单ID | | `created_at` | DATETIME | — | 创建时间 | 索引:`ix_role_menus_menu_id`(menu_id);`ix_role_menus_role_id`(role_id) ## 项目与文档 ### `projects` — 项目 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 项目ID | | `name` | VARCHAR(100) | NOT NULL | 项目名称 | | `description` | VARCHAR(500) | — | 项目描述 | | `storage_key` | VARCHAR(36) | UNIQUE · NOT NULL | 磁盘存储UUID | | `owner_id` | BIGINT | NOT NULL | 项目所有者ID | | `is_public` | SMALLINT | 默认 `0` | 是否公开:0-私有 1-公开 | | `is_template` | SMALLINT | 默认 `0` | 是否模板项目:0-否 1-是 | | `status` | SMALLINT | 默认 `1` | 状态:0-归档 1-活跃 | | `cover_image` | VARCHAR(255) | — | 封面图 | | `sort_order` | INTEGER | 默认 `0` | 排序号 | | `visit_count` | INTEGER | 默认 `0` | 访问次数 | | `access_pass` | VARCHAR(100) | — | 访问密码(用于分享链接) | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_projects_created_at`(created_at);`ix_projects_name`(name);`ix_projects_owner_id`(owner_id);`ix_projects_status`(status) ### `project_members` — 项目成员 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 成员ID | | `project_id` | BIGINT | NOT NULL | 项目ID | | `user_id` | BIGINT | NOT NULL | 用户ID | | `role` | VARCHAR(20) | 默认 `viewer` | 项目角色: admin/editor/viewer | | `invited_by` | BIGINT | — | 邀请人ID | | `joined_at` | DATETIME | — | 加入时间 | 索引:`ix_project_members_project_id`(project_id);`ix_project_members_role`(role);`ix_project_members_user_id`(user_id) ### `document_meta` — 文档元数据 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 元数据ID | | `project_id` | BIGINT | NOT NULL | 项目ID | | `file_path` | VARCHAR(500) | NOT NULL | 文件相对路径 | | `title` | VARCHAR(200) | — | 文档标题 | | `tags` | VARCHAR(500) | — | 标签(JSON数组) | | `author_id` | BIGINT | — | 作者ID | | `word_count` | INTEGER | 默认 `0` | 字数统计 | | `view_count` | INTEGER | 默认 `0` | 浏览次数 | | `last_editor_id` | BIGINT | — | 最后编辑者ID | | `last_edited_at` | DATETIME | — | 最后编辑时间 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_document_meta_author_id`(author_id);`ix_document_meta_project_id`(project_id) ### `project_git_repos` — 项目绑定的 Git 仓库 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | ID | | `project_id` | BIGINT | FK → projects.id · NOT NULL | 项目ID | | `name` | VARCHAR(50) | NOT NULL | 仓库别名 | | `repo_url` | VARCHAR(255) | NOT NULL | Git仓库地址 | | `branch` | VARCHAR(50) | 默认 `main` | Git分支 | | `username` | VARCHAR(100) | — | Git用户名 | | `token` | VARCHAR(255) | — | Git访问令牌/密码 | | `is_default` | SMALLINT | 默认 `0` | 是否默认仓库 | | `sync_path` | VARCHAR(255) | — | 同步目录(空=整个仓库) | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_project_git_repos_project_id`(project_id) ### `share_links` — 分享链接 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 分享ID | | `project_id` | BIGINT | NOT NULL | 项目ID | | `share_type` | VARCHAR(20) | NOT NULL | 分享类型: project/file | | `share_code` | VARCHAR(64) | UNIQUE · NOT NULL | 公开分享码 | | `file_path` | VARCHAR(500) | — | 文件路径,仅文件分享使用 | | `access_pass` | VARCHAR(100) | — | 访问密码 | | `created_by` | BIGINT | — | 创建人ID | | `status` | SMALLINT | 默认 `1` | 状态:0-禁用 1-启用 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_share_links_created_by`(created_by);`ix_share_links_project_id`(project_id);`ix_share_links_share_code`(share_code) UNIQUE;`ix_share_links_share_type`(share_type);`ix_share_links_status`(status) ## AI 知识库 ### `llm_model_config` — LLM 模型配置 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `config_id` | BIGINT | PK | 配置ID | | `model_code` | VARCHAR(128) | UNIQUE · NOT NULL | 模型编码 | | `model_name` | VARCHAR(255) | NOT NULL | 模型名称 | | `model_type` | VARCHAR(32) | NOT NULL · 默认 `chat` | 模型类型: chat/embedding | | `provider` | VARCHAR(64) | — | 模型提供方 | | `endpoint_url` | VARCHAR(512) | — | 接口地址 | | `api_key` | VARCHAR(512) | — | API Key | | `llm_model_name` | VARCHAR(128) | NOT NULL | 模型名称/部署名 | | `llm_timeout` | INTEGER | NOT NULL · 默认 `120` | 超时时间(秒) | | `type_config` | JSON | NOT NULL · 默认 `服务端函数` | 模型类型差异参数 | | `description` | VARCHAR(500) | — | 描述 | | `is_active` | BOOLEAN | NOT NULL · 默认 `True` | 是否启用 | | `is_default` | BOOLEAN | NOT NULL · 默认 `False` | 是否默认 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_llm_model_config_is_active`(is_active);`ix_llm_model_config_model_code`(model_code) UNIQUE;`ix_llm_model_config_model_type`(model_type) ### `chat_session` — 对话会话 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 会话ID | | `project_id` | BIGINT | NOT NULL | 项目ID | | `user_id` | BIGINT | NOT NULL | 用户ID | | `llm_config_id` | BIGINT | NOT NULL | LLM配置ID | | `title` | VARCHAR(255) | NOT NULL | 会话标题 | | `description` | TEXT | — | 会话描述 | | `is_active` | BOOLEAN | NOT NULL · 默认 `True` | 是否激活 | | `message_count` | INTEGER | NOT NULL · 默认 `0` | 消息数 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`ix_chat_session_project_id`(project_id);`ix_chat_session_user_id`(user_id) ### `chat_message` — 对话消息 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 消息ID | | `session_id` | BIGINT | NOT NULL | 会话ID | | `role` | VARCHAR(32) | NOT NULL | 角色(user/assistant) | | `content` | TEXT | NOT NULL | 消息内容 | | `status` | VARCHAR(32) | NOT NULL · 默认 `pending` | 消息状态: pending/completed/interrupted/error | | `duration_ms` | INTEGER | — | 生成耗时(毫秒) | | `thinking_log` | TEXT | — | 思考过程(JSON数组) | | `referenced_files` | TEXT | — | 参考文件(JSON数组) | | `tokens_used` | INTEGER | — | 消耗的token数 | | `is_deleted` | BOOLEAN | NOT NULL · 默认 `False` | 是否已删除 | | `created_at` | DATETIME | — | 创建时间 | 索引:`ix_chat_message_session_id`(session_id) ### `document_vector` — 文档向量分块 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 向量ID | | `project_id` | BIGINT | NOT NULL | 项目ID | | `file_path` | VARCHAR(500) | NOT NULL | 文件相对路径 | | `chunk_index` | INTEGER | NOT NULL · 默认 `0` | 分块序号(0起),同一文件可有多个分块 | | `chunk_text` | TEXT | — | 分块首段文本,作为点击引用时的定位锚点 | | `content_hash` | VARCHAR(64) | — | 整个文件内容哈希值,用于判断文件是否变更 | | `zvec_id` | VARCHAR(256) | — | ZVec返回的向量ID(每个分块独立) | | `zvec_response` | TEXT | — | ZVec完整响应JSON | | `status` | VARCHAR(32) | NOT NULL · 默认 `success` | 向量化状态:success/failed/pending | | `error_message` | VARCHAR(500) | — | 错误信息 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`idx_project_file_chunk`(project_id, file_path, chunk_index);`idx_project_file`(project_id, file_path);`ix_document_vector_project_id`(project_id) ### `project_vectorization_task` — 项目向量化任务 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 任务ID | | `task_id` | VARCHAR(64) | UNIQUE · NOT NULL | 任务唯一标识 | | `project_id` | BIGINT | NOT NULL | 项目ID | | `user_id` | BIGINT | NOT NULL | 触发用户ID | | `task_type` | VARCHAR(32) | NOT NULL | 任务类型:incremental/full | | `status` | VARCHAR(32) | NOT NULL · 默认 `pending` | 任务状态:pending/running/success/failed | | `total` | INTEGER | NOT NULL · 默认 `0` | 文件总数 | | `processed` | INTEGER | NOT NULL · 默认 `0` | 处理成功数 | | `skipped` | INTEGER | NOT NULL · 默认 `0` | 跳过数 | | `failed` | INTEGER | NOT NULL · 默认 `0` | 失败数 | | `error_message` | TEXT | — | 错误信息 | | `started_at` | DATETIME | — | 开始时间 | | `finished_at` | DATETIME | — | 完成时间 | | `created_at` | DATETIME | — | 创建时间 | | `updated_at` | DATETIME | — | 更新时间 | 索引:`idx_vector_task_created_at`(created_at);`idx_vector_task_project_status`(project_id, status);`ix_project_vectorization_task_project_id`(project_id);`ix_project_vectorization_task_status`(status);`ix_project_vectorization_task_task_id`(task_id) UNIQUE;`ix_project_vectorization_task_user_id`(user_id) ## 通知与审计 ### `notifications` — 站内通知 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 通知ID | | `user_id` | BIGINT | FK → users.id · NOT NULL | 接收用户ID | | `type` | VARCHAR(20) | 默认 `info` | 类型:info, success, warning, error | | `category` | VARCHAR(50) | 默认 `system` | 分类:system, project, collaboration | | `title` | VARCHAR(200) | NOT NULL | 标题 | | `content` | TEXT | — | 内容 | | `link` | VARCHAR(255) | — | 跳转链接 | | `is_read` | SMALLINT | 默认 `0` | 是否已读:0-未读 1-已读 | | `created_at` | DATETIME | — | 创建时间 | | `read_at` | DATETIME | — | 阅读时间 | 索引:`ix_notifications_user_id`(user_id) ### `operation_logs` — 操作日志 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | 日志ID | | `user_id` | BIGINT | — | 操作用户ID | | `username` | VARCHAR(50) | — | 用户名 | | `operation_type` | VARCHAR(50) | NOT NULL | 操作类型 | | `resource_type` | VARCHAR(50) | NOT NULL | 资源类型 | | `resource_id` | BIGINT | — | 资源ID | | `detail` | TEXT | — | 操作详情(JSON) | | `ip_address` | VARCHAR(50) | — | IP地址 | | `user_agent` | VARCHAR(500) | — | 用户代理 | | `status` | SMALLINT | 默认 `1` | 状态:0-失败 1-成功 | | `error_message` | TEXT | — | 错误信息 | | `created_at` | DATETIME | — | 操作时间 | 索引:`ix_operation_logs_created_at`(created_at);`ix_operation_logs_resource_id`(resource_id);`ix_operation_logs_resource_type`(resource_type);`ix_operation_logs_user_id`(user_id) ### `mcp_bots` — MCP Bot 凭证 | 字段 | 类型 | 约束/默认 | 说明 | | --- | --- | --- | --- | | `id` | BIGINT | PK | Bot credential ID | | `user_id` | BIGINT | UNIQUE · NOT NULL | Owner user ID | | `bot_id` | VARCHAR(64) | UNIQUE · NOT NULL | External MCP bot id | | `bot_secret` | VARCHAR(255) | NOT NULL | External MCP bot secret | | `status` | SMALLINT | 默认 `1` | Status: 0-disabled 1-enabled | | `last_used_at` | DATETIME | — | Last successful MCP access time | | `created_at` | DATETIME | — | Created at | | `updated_at` | DATETIME | — | Updated at | 索引:`ix_mcp_bots_bot_id`(bot_id) UNIQUE;`ix_mcp_bots_status`(status);`ix_mcp_bots_user_id`(user_id) UNIQUE --- ## 变更记录 | 日期 | 变更 | | --- | --- | | 2026-09-30 | 按 v1.0.0 代码重写:补齐 `notifications`、`mcp_bots`、`project_git_repos`、`chat_*`、`document_vector`、`project_vectorization_task` 等 10 张此前未记录的表;删除已废弃的 `init_database.sql` 相关描述 |