Skip to content

Latest commit

 

History

History
520 lines (435 loc) · 16.4 KB

File metadata and controls

520 lines (435 loc) · 16.4 KB

数据库设计文档

存储驱动

CareerTrack 支持两种数据库后端,默认使用 SQLite(零配置),可选 PostgreSQL。

驱动 环境变量 说明
SQLite STORAGE_DRIVER=sqlite(默认) 零配置本地存储,数据库文件 .careertrack/careertrack.db
PostgreSQL STORAGE_DRIVER=postgres 需要 DATABASE_URL 环境变量

驱动选择逻辑

  1. 设置 STORAGE_DRIVER=sqlite → SQLite(显式)
  2. 设置 STORAGE_DRIVER=postgres → PostgreSQL(显式)
  3. 未设置 STORAGE_DRIVER,有 DATABASE_URL → PostgreSQL(向后兼容)
  4. 未设置 STORAGE_DRIVER,无 DATABASE_URL → SQLite(默认)

环境变量

变量 必填 默认值 说明
STORAGE_DRIVER 自动检测 sqlitepostgres
DATABASE_URL postgres 模式必填 - PostgreSQL 连接串
SQLITE_DB_PATH .careertrack/careertrack.db SQLite 数据库文件路径

表结构设计(SQLite)

SQLite 使用以下类型映射:UUID → TEXTJSONB → TEXTBOOLEAN → INTEGERTIMESTAMP → TEXT

首次启动时自动建表,无需手动执行迁移。

-- 用户表
CREATE TABLE IF NOT EXISTS users (
    id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(4))) || '-' || ...),  -- UUID v4
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255),            -- GitHub-only 用户可为 NULL
    otp_secret VARCHAR(100),
    otp_enabled INTEGER DEFAULT 0,
    role VARCHAR(20) NOT NULL DEFAULT 'user',
    auth_provider INTEGER NOT NULL DEFAULT 1,  -- 登录方式位掩码:PASSWORD=1, GITHUB=2
    disabled_at TEXT,                      -- 禁用时间,NULL 表示正常
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);

-- 个人信息表
CREATE TABLE IF NOT EXISTS profiles (
    id TEXT PRIMARY KEY DEFAULT ...,
    user_id TEXT UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    basic_info TEXT DEFAULT '{}',      -- JSON 对象
    education TEXT DEFAULT '[]',       -- JSON 数组
    skills TEXT DEFAULT '[]',
    work_experience TEXT DEFAULT '[]',
    projects TEXT DEFAULT '[]',
    portfolio TEXT DEFAULT '[]',
    awards TEXT DEFAULT '[]',
    other_experience TEXT DEFAULT '[]',
    research TEXT DEFAULT '[]',
    summary TEXT DEFAULT '',
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);

-- 简历表
CREATE TABLE IF NOT EXISTS resumes (
    id TEXT PRIMARY KEY DEFAULT ...,
    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(100) NOT NULL DEFAULT '未命名简历',
    modules_config TEXT DEFAULT '{...}',   -- JSON 对象
    content TEXT DEFAULT '{}',             -- JSON 对象
    is_public INTEGER DEFAULT 0,
    public_slug VARCHAR(50) UNIQUE,
    modules_order TEXT DEFAULT '[...]',    -- JSON 数组
    module_titles TEXT DEFAULT '{}',       -- JSON 对象,自定义模块标题
    basic_info_display TEXT DEFAULT '{}',  -- JSON 对象,字段图标/头像位置
    preview_config TEXT DEFAULT '{}',      -- JSON 对象,字号/行距
    template VARCHAR(20) DEFAULT 'classic',
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);

-- MCP Key 表
CREATE TABLE IF NOT EXISTS mcp_keys (
    id TEXT PRIMARY KEY DEFAULT ...,
    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    prefix VARCHAR(20) NOT NULL,
    hash VARCHAR(64) NOT NULL,
    scope VARCHAR(20) NOT NULL DEFAULT 'read_write',
    created_at TEXT DEFAULT (datetime('now')),
    last_used_at TEXT,
    revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_mcp_keys_user_id ON mcp_keys(user_id);
CREATE INDEX IF NOT EXISTS idx_mcp_keys_hash ON mcp_keys(hash);

-- OAuth 账号绑定表
CREATE TABLE IF NOT EXISTS user_oauth_accounts (
    id TEXT PRIMARY KEY DEFAULT ...,
    user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    provider VARCHAR(50) NOT NULL,         -- 提供商:github
    provider_account_id VARCHAR(255) NOT NULL,  -- 第三方平台用户 ID
    provider_username VARCHAR(255),        -- 第三方平台用户名
    email VARCHAR(255),                    -- 第三方平台邮箱
    avatar_url TEXT,                       -- 第三方平台头像
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now')),
    UNIQUE(provider, provider_account_id)
);
CREATE INDEX IF NOT EXISTS idx_user_oauth_user_id ON user_oauth_accounts(user_id);

-- 注册码表
CREATE TABLE IF NOT EXISTS registration_codes (
    id TEXT PRIMARY KEY DEFAULT ...,
    code_hash VARCHAR(64) NOT NULL UNIQUE, -- 注册码 SHA-256 哈希
    label VARCHAR(100),                    -- 备注标签
    created_by TEXT REFERENCES users(id),  -- 创建者
    used_by_user_id TEXT REFERENCES users(id),  -- 使用者
    expires_at TEXT,                       -- 过期时间,NULL 表示永不过期
    disabled_at TEXT,                      -- 禁用时间,NULL 表示可用
    used_at TEXT,                          -- 使用时间
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_registration_codes_hash ON registration_codes(code_hash);

完整的建表 SQL 见 src/lib/storage/schema.ts

表结构设计(PostgreSQL)

users 用户表

存储用户账号信息和认证数据。

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(255),            -- GitHub-only 用户可为 NULL
    otp_secret VARCHAR(100),           -- TOTP 密钥,NULL 表示未启用 OTP
    otp_enabled BOOLEAN DEFAULT FALSE,
    role VARCHAR(20) NOT NULL DEFAULT 'user',  -- 用户角色:user / admin
    auth_provider INTEGER NOT NULL DEFAULT 1,  -- 登录方式位掩码:PASSWORD=1, GITHUB=2, OTHER=4
    disabled_at TIMESTAMP WITH TIME ZONE,  -- 禁用时间,NULL 表示正常
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_users_username ON users(username);

字段说明:

  • id: 用户唯一标识,使用 UUID 避免 ID 猜测
  • username: 登录用户名,唯一
  • password_hash: bcrypt 加密后的密码,GitHub-only 用户可为 NULL
  • otp_secret: TOTP 密钥,启用 OTP 时生成
  • otp_enabled: 是否启用 OTP 二次验证
  • role: 用户角色,user(普通用户)或 admin(管理员),新注册默认 user
  • auth_provider: 登录方式位掩码,1=密码, 2=GitHub, 4=其他
  • disabled_at: 禁用时间戳,NULL 表示账号正常

profiles 个人信息表

存储用户的个人信息,每种经历类型使用 JSONB 数组存储。

CREATE TABLE profiles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,

    -- 基本信息
    basic_info JSONB DEFAULT '{}',

    -- 各类经历(数组形式)
    education JSONB DEFAULT '[]',          -- 教育经历
    skills JSONB DEFAULT '[]',             -- 专业技能
    work_experience JSONB DEFAULT '[]',    -- 工作经历
    projects JSONB DEFAULT '[]',           -- 项目经历
    portfolio JSONB DEFAULT '[]',          -- 个人作品
    awards JSONB DEFAULT '[]',             -- 荣誉奖项
    other_experience JSONB DEFAULT '[]',   -- 其他经历
    research JSONB DEFAULT '[]',           -- 研究经历

    -- 个人简介
    summary TEXT DEFAULT '',

    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_profiles_user_id ON profiles(user_id);

JSONB 字段结构:

// basic_info
{
  "name": "张三",
  "phone": "13800138000",
  "email": "zhangsan@example.com",
  "avatar": "/uploads/avatar.jpg",
  "job_intention": {
    "current_status": "在职",
    "position": "前端工程师",
    "expected_city": "北京",
    "expected_salary": "20-30K"
  },
  "other": {
    "education_level": "本科",
    "website": "https://example.com",
    "wechat": "zhangsan",
    "city": "北京",
    "github": "https://github.com/zhangsan",
    "age": 28,
    "work_years": 5,
    "gender": ""
  }
}

// education 数组中的单个对象
{
  "id": "uuid",
  "school": "北京大学",
  "major": "计算机科学",
  "degree": "本科",
  "start_date": "2016-09",
  "end_date": "2020-06",
  "degree_type": "全日制",
  "college": "计算机学院",
  "city": "北京",
  "description": "在校经历描述"
}

// skills 数组中的单个对象
{
  "id": "uuid",
  "name": "JavaScript",
  "description": "熟练掌握 ES6+,有丰富的 React 开发经验"
}

// work_experience 数组中的单个对象
{
  "id": "uuid",
  "company": "字节跳动",
  "start_date": "2020-07",
  "end_date": null,  // null 表示"至今"
  "department": "前端团队",
  "position": "高级前端工程师",
  "city": "北京",
  "description": "工作内容描述"
}

// projects 数组中的单个对象
{
  "id": "uuid",
  "name": "电商平台重构",
  "start_date": "2021-01",
  "end_date": "2021-06",
  "role": "前端负责人",
  "city": "北京",
  "link": "https://project-url.com",
  "description": "项目描述"
}

// portfolio 数组中的单个对象
{
  "id": "uuid",
  "name": "开源项目",
  "link": "https://github.com/...",
  "image": "/uploads/portfolio.jpg",
  "description": "项目描述"
}

// awards 数组中的单个对象
{
  "id": "uuid",
  "name": "最佳员工",
  "date": "2023-12",
  "description": "获奖描述"
}

// other_experience 和 research 结构类似
{
  "id": "uuid",
  "name": "经历名称",
  "start_date": "2020-01",
  "end_date": "2020-06",
  "role": "角色",
  "department": "部门",
  "city": "城市",
  "description": "详细描述"
}

resumes 简历表

存储简历配置和内容。

CREATE TABLE resumes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name VARCHAR(100) NOT NULL DEFAULT '未命名简历',
    template VARCHAR(20) DEFAULT 'classic',

    -- 模块开关配置
    modules_config JSONB DEFAULT '{
        "basic_info": true,
        "education": true,
        "skills": true,
        "work_experience": true,
        "projects": true,
        "portfolio": false,
        "awards": false,
        "other_experience": false,
        "research": false,
        "summary": false
    }',

    -- 简历内容(可以覆盖个人信息)
    content JSONB DEFAULT '{}',

    -- 公开相关
    is_public BOOLEAN DEFAULT FALSE,
    public_slug VARCHAR(50) UNIQUE,

    -- 模块排序
    modules_order JSONB DEFAULT '["basic_info", "education", "skills", "work_experience", "projects", "portfolio", "awards", "other_experience", "research", "summary"]',

    -- 自定义模块标题
    module_titles JSONB DEFAULT '{}',

    -- 基本信息显示配置
    basic_info_display JSONB DEFAULT '{}',

    -- 预览配置(字号、行距等)
    preview_config JSONB DEFAULT '{}',

    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_resumes_user_id ON resumes(user_id);
CREATE INDEX idx_resumes_public_slug ON resumes(public_slug) WHERE public_slug IS NOT NULL;
CREATE INDEX idx_resumes_template ON resumes(template);

mcp_keys MCP Key 表

存储 MCP 服务的 API Key,用于 AI Agent 访问。

CREATE TABLE mcp_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    prefix VARCHAR(20) NOT NULL,        -- Key 前缀,用于列表展示
    hash VARCHAR(64) NOT NULL,           -- Key 的 SHA-256 哈希
    scope VARCHAR(20) NOT NULL DEFAULT 'read_write',  -- 权限范围:read_write / read_only
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_used_at TIMESTAMP WITH TIME ZONE,  -- 最后使用时间
    revoked_at TIMESTAMP WITH TIME ZONE     -- 撤销时间,NULL 表示有效
);

-- 索引
CREATE INDEX idx_mcp_keys_user_id ON mcp_keys(user_id);
CREATE INDEX idx_mcp_keys_hash ON mcp_keys(hash);

user_oauth_accounts OAuth 绑定表

存储用户的第三方 OAuth 账号绑定关系。

CREATE TABLE user_oauth_accounts (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    provider VARCHAR(50) NOT NULL,             -- 提供商:github
    provider_account_id VARCHAR(255) NOT NULL, -- 第三方平台用户 ID
    provider_username VARCHAR(255),            -- 第三方平台用户名
    email VARCHAR(255),                        -- 第三方平台邮箱
    avatar_url TEXT,                           -- 第三方平台头像
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(provider, provider_account_id)
);

-- 索引
CREATE INDEX idx_user_oauth_user_id ON user_oauth_accounts(user_id);

字段说明:

  • provider: OAuth 提供商标识,如 github
  • provider_account_id: 第三方平台的用户唯一标识
  • provider_username: 第三方平台的用户名
  • email: 第三方平台的邮箱地址
  • avatar_url: 第三方平台的头像 URL

registration_codes 注册码表

存储邀请注册码,用于控制用户注册。

CREATE TABLE registration_codes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    code_hash VARCHAR(64) NOT NULL UNIQUE,   -- 注册码 SHA-256 哈希
    label VARCHAR(100),                      -- 备注标签
    created_by UUID REFERENCES users(id),    -- 创建者(管理员)
    used_by_user_id UUID REFERENCES users(id), -- 使用者
    expires_at TIMESTAMP WITH TIME ZONE,     -- 过期时间,NULL 表示永不过期
    disabled_at TIMESTAMP WITH TIME ZONE,    -- 禁用时间,NULL 表示可用
    used_at TIMESTAMP WITH TIME ZONE,        -- 使用时间
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_registration_codes_hash ON registration_codes(code_hash);

字段说明:

  • code_hash: 注册码明文的 SHA-256 哈希,明文只在创建时返回一次
  • label: 管理员备注,方便识别注册码用途
  • created_by: 创建该注册码的管理员
  • used_by_user_id: 使用该注册码注册的用户
  • expires_at: 过期时间,NULL 表示永不过期
  • disabled_at: 禁用时间,非 NULL 表示已禁用

content 字段说明:

content 字段用于存储简历的自定义内容。结构与 profiles 类似,但每个字段都是可选的:

  • 如果某个字段存在,使用 content 中的值
  • 如果某个字段不存在或为 null,自动使用 profile 中的值
{
  "basic_info": { ... },     // 可选,覆盖个人信息
  "education": [ ... ],      // 可选,覆盖个人信息
  "skills": [ ... ],         // 可选,覆盖个人信息
  // ... 其他字段同理
}

数据关系

users (1) ──── (1) profiles
  │
  ├──── (N) resumes
  │
  ├──── (N) mcp_keys
  │
  ├──── (N) user_oauth_accounts
  │
  └──── created (N) registration_codes
  • 一个用户对应一份个人信息(1:1)
  • 一个用户可以有多份简历(1:N)
  • 一个用户可以有多个 MCP Key(1:N)
  • 一个用户可以绑定多个 OAuth 账号(1:N)
  • 一个管理员可以创建多个注册码(1:N)
  • 简历内容可以引用或覆盖个人信息

扩展性设计

1. 模块扩展

modules_config 使用 JSONB,可以轻松添加新模块:

{
  "custom_module": true,
  "certificates": true
}

2. AI 功能扩展

未来可在 content 中添加 AI 生成的内容标记:

{
  "skills": [
    {
      "id": "uuid",
      "name": "JavaScript",
      "description": "AI 优化后的描述",
      "ai_optimized": true
    }
  ]
}

3. 模版系统扩展

未来可添加 templates 表:

CREATE TABLE templates (
    id UUID PRIMARY KEY,
    name VARCHAR(100),
    content JSONB,
    -- ...
);

迁移管理

SQLite

SQLite 使用自动建表策略。首次启动时,src/lib/storage/schema.ts 中的 initSchema() 函数会自动执行 CREATE TABLE IF NOT EXISTS 创建所有表。

无需手动执行迁移。

PostgreSQL

PostgreSQL 使用相同的自动建表策略。首次启动时自动创建所有表和索引。