返回笔记首页

数据库设计文档

主题配置

数据库概览

技术选型

Supabase (PostgreSQL 14)

选择理由:

  • ✅ 基于 PostgreSQL,功能强大
  • ✅ 内置认证系统(Auth)
  • ✅ 实时订阅功能(Realtime)
  • ✅ 向量存储支持(pgvector)
  • ✅ 免费额度充足(500MB 数据库)
  • ✅ 自动生成 REST API

表结构设计

1. users 表(用户表)

说明:使用 Supabase Auth,自动创建

sql
-- Supabase 自动管理
-- auth.users 表包含:
-- - id (UUID)
-- - email (VARCHAR)
-- - created_at (TIMESTAMPTZ)
-- - updated_at (TIMESTAMPTZ)

关键点

  • Supabase Auth 自动处理用户注册、登录
  • 不需要手动创建用户表
  • 其他表通过 user_id 关联到 auth.users(id)

2. projects 表(项目表)

用途:存储用户创建的内容项目

sql
CREATE TABLE projects (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 用户关联(外键到 auth.users)
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL,

  -- 基本信息
  title VARCHAR(255) NOT NULL,                    -- 项目标题
  description TEXT,                               -- 项目描述

  -- 类型和状态
  type VARCHAR(50) DEFAULT 'blog' NOT NULL,       -- 文章类型
  status VARCHAR(50) DEFAULT 'draft' NOT NULL,    -- 项目状态

  -- 创作配置
  target_audience TEXT,                           -- 目标受众
  word_count_target INTEGER DEFAULT 2000,         -- 目标字数
  tone VARCHAR(50) DEFAULT 'professional',        -- 语气风格

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,

  -- 索引
  CONSTRAINT projects_type_check CHECK (
    type IN ('blog', 'xiaohongshu', 'article', 'weixin')
  ),
  CONSTRAINT projects_status_check CHECK (
    status IN ('draft', 'planning', 'writing', 'optimizing', 'completed')
  ),
  CONSTRAINT projects_tone_check CHECK (
    tone IN ('professional', 'casual', 'humorous')
  )
);

-- 创建索引
CREATE INDEX idx_projects_user_id ON projects(user_id);
CREATE INDEX idx_projects_status ON projects(status);
CREATE INDEX idx_projects_created_at ON projects(created_at DESC);

-- 创建复合索引(优化查询)
CREATE INDEX idx_projects_user_status ON projects(user_id, status);

字段说明

字段 类型 说明 示例
id UUID 主键,自动生成 123e4567-e89b-12d3-a456-426614174000
user_id UUID 所属用户 auth.users.id
title VARCHAR(255) 项目标题 "AI 在前端开发中的应用"
type VARCHAR(50) 文章类型 blog
, xiaohongshu
, article
, weixin
status VARCHAR(50) 项目状态 draft
, planning
, writing
, completed
word_count_target INTEGER 目标字数 2000
tone VARCHAR(50) 语气风格 professional
, casual
, humorous

约束说明

  • ON DELETE CASCADE:用户删除时,自动删除其所有项目
  • CHECK 约束:确保枚举值正确
  • 索引优化:提升查询性能

3. outlines 表(大纲表)

用途:存储规划 Agent 生成的文章大纲

sql
CREATE TABLE outlines (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 项目关联(外键)
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE NOT NULL,

  -- 大纲内容(JSONB 格式,灵活存储结构化数据)
  content JSONB NOT NULL,

  -- 版本控制
  version INTEGER DEFAULT 1 NOT NULL,

  -- SEO 信息
  keywords TEXT[] DEFAULT '{}',                   -- 关键词数组
  seo_title VARCHAR(255),                         -- SEO 标题
  meta_description VARCHAR(160),                  -- 元描述

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,

  -- 索引
  CONSTRAINT outlines_version_check CHECK (version >= 1)
);

-- 创建索引
CREATE INDEX idx_outlines_project_id ON outlines(project_id);
CREATE INDEX idx_outlines_created_at ON outlines(created_at DESC);

-- GIN 索引(用于 JSONB 查询)
CREATE INDEX idx_outlines_content ON outlines USING GIN (content);

-- 创建全文搜索索引(关键词搜索)
CREATE INDEX idx_outlines_keywords ON outlines USING GIN (keywords);

content 字段结构(JSONB):

json
{
  "title": "文章标题",
  "introduction": {
    "hook": "开场白",
    "thesis": "核心论点",
    "wordCount": 200
  },
  "sections": [
    {
      "heading": "第一章节",
      "points": ["要点1", "要点2"],
      "wordCount": 500,
      "tips": "写作建议"
    }
  ],
  "conclusion": {
    "summary": "总结",
    "callToAction": "行动号召",
    "wordCount": 300
  }
}

设计亮点

  • JSONB 存储:灵活的数据结构,无需预定义所有字段
  • 版本控制:支持多个版本的大纲
  • GIN 索引:支持 JSONB 内部字段的快速查询
  • 数组类型:PostgreSQL 原生支持,高效存储关键词

面试要点

plain
"我使用 JSONB 而不是 JSON,因为 JSONB 是二进制格式,查询性能更好。
而且 PostgreSQL 的 GIN 索引可以对 JSONB 内部字段建立索引,
支持高效的复杂查询。"

4. sections 表(章节表)

用途:存储每个章节的具体内容

sql
CREATE TABLE sections (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 关联
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE NOT NULL,
  outline_id UUID REFERENCES outlines(id) ON DELETE SET NULL,

  -- 顺序和标题
  order_index INTEGER NOT NULL,                   -- 章节顺序(从 0 开始)
  heading VARCHAR(255) NOT NULL,                  -- 章节标题

  -- 内容
  content TEXT,                                   -- 章节内容(Markdown 格式)
  word_count INTEGER DEFAULT 0 NOT NULL,          -- 实际字数

  -- 状态
  status VARCHAR(50) DEFAULT 'pending' NOT NULL,

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
  updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,

  -- 约束
  CONSTRAINT sections_status_check CHECK (
    status IN ('pending', 'writing', 'completed')
  ),
  CONSTRAINT sections_order_check CHECK (order_index >= 0),

  -- 唯一约束:同一项目内章节顺序不能重复
  UNIQUE(project_id, order_index)
);

-- 创建索引
CREATE INDEX idx_sections_project_id ON sections(project_id);
CREATE INDEX idx_sections_outline_id ON sections(outline_id);
CREATE INDEX idx_sections_status ON sections(status);

-- 复合索引(用于排序查询)
CREATE INDEX idx_sections_project_order ON sections(project_id, order_index);

字段说明

字段 类型 说明
order_index INTEGER 章节顺序,从 0 开始
heading VARCHAR(255) 章节标题,如 "什么是 AI Agent"
content TEXT Markdown 格式的内容
word_count INTEGER 实际字数,自动计算
status VARCHAR(50) pending
, writing
, completed

唯一约束说明

sql
UNIQUE(project_id, order_index)

确保同一项目内章节顺序不重复,防止数据错误。


5. optimizations 表(优化建议表)

用途:存储优化 Agent 的分析结果和建议

sql
CREATE TABLE optimizations (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 关联
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE NOT NULL,
  section_id UUID REFERENCES sections(id) ON DELETE CASCADE,

  -- 优化类型
  type VARCHAR(50) NOT NULL,                      -- 优化类型

  -- 原始文本和建议(JSONB 存储复杂结构)
  original_text TEXT,                             -- 原始文本
  suggestions JSONB NOT NULL,                     -- 优化建议

  -- 应用状态
  applied BOOLEAN DEFAULT FALSE NOT NULL,         -- 是否已应用

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,

  -- 约束
  CONSTRAINT optimizations_type_check CHECK (
    type IN ('grammar', 'seo', 'readability', 'tone', 'comprehensive')
  )
);

-- 创建索引
CREATE INDEX idx_optimizations_project_id ON optimizations(project_id);
CREATE INDEX idx_optimizations_section_id ON optimizations(section_id);
CREATE INDEX idx_optimizations_type ON optimizations(type);
CREATE INDEX idx_optimizations_applied ON optimizations(applied);

-- GIN 索引(JSONB 查询)
CREATE INDEX idx_optimizations_suggestions ON optimizations USING GIN (suggestions);

suggestions 字段结构(JSONB):

json
{
  "overallScore": 85,
  "readability": {
    "score": 82,
    "issues": ["句子过长", "段落不够简洁"],
    "suggestions": ["建议拆分长句", "每段控制在 3-5 句"]
  },
  "grammar": {
    "errors": [
      {
        "text": "它的功能",
        "correction": "它的功能是",
        "explanation": "句子不完整"
      }
    ]
  },
  "seo": {
    "score": 88,
    "keywordDensity": {
      "AI": 2.5,
      "前端": 1.8
    },
    "suggestions": ["增加关键词密度"]
  }
}

6. images 表(图片表)

用途:存储配图信息

sql
CREATE TABLE images (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 关联
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE NOT NULL,
  section_id UUID REFERENCES sections(id) ON DELETE CASCADE,

  -- 图片信息
  prompt TEXT NOT NULL,                           -- 生成图片的 Prompt
  url TEXT,                                       -- 图片 URL
  thumbnail_url TEXT,                             -- 缩略图 URL
  alt_text VARCHAR(255),                          -- Alt 文本(SEO)

  -- 元数据
  width INTEGER,                                  -- 宽度(像素)
  height INTEGER,                                 -- 高度(像素)
  file_size INTEGER,                              -- 文件大小(字节)

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);

-- 创建索引
CREATE INDEX idx_images_project_id ON images(project_id);
CREATE INDEX idx_images_section_id ON images(section_id);

7. agent_logs 表(Agent 执行日志表)

用途:记录每次 Agent 调用的日志,用于调试和分析

sql
CREATE TABLE agent_logs (
  -- 主键
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),

  -- 关联
  project_id UUID REFERENCES projects(id) ON DELETE CASCADE NOT NULL,

  -- Agent 信息
  agent_type VARCHAR(50) NOT NULL,                -- Agent 类型

  -- 输入输出(JSONB 存储)
  input JSONB NOT NULL,                           -- 输入参数
  output JSONB,                                   -- 输出结果

  -- 性能指标
  tokens_used INTEGER,                            -- 使用的 token 数
  duration_ms INTEGER,                            -- 执行时间(毫秒)

  -- 状态和错误
  status VARCHAR(50) NOT NULL,                    -- 执行状态
  error_message TEXT,                             -- 错误信息

  -- 时间戳
  created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,

  -- 约束
  CONSTRAINT agent_logs_type_check CHECK (
    agent_type IN ('planner', 'writer', 'optimizer', 'illustrator')
  ),
  CONSTRAINT agent_logs_status_check CHECK (
    status IN ('success', 'error')
  )
);

-- 创建索引
CREATE INDEX idx_agent_logs_project_id ON agent_logs(project_id);
CREATE INDEX idx_agent_logs_agent_type ON agent_logs(agent_type);
CREATE INDEX idx_agent_logs_status ON agent_logs(status);
CREATE INDEX idx_agent_logs_created_at ON agent_logs(created_at DESC);

-- 复合索引(性能分析)
CREATE INDEX idx_agent_logs_type_status ON agent_logs(agent_type, status);

-- GIN 索引(JSONB 查询)
CREATE INDEX idx_agent_logs_input ON agent_logs USING GIN (input);
CREATE INDEX idx_agent_logs_output ON agent_logs USING GIN (output);

用途说明

  1. 调试:快速定位问题
  2. 性能分析:统计平均执行时间
  3. 成本核算:统计 token 使用量
  4. 质量监控:分析成功率和失败原因

示例查询

sql
-- 查询平均执行时间
SELECT
  agent_type,
  AVG(duration_ms) as avg_duration,
  COUNT(*) as total_calls,
  SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success_count
FROM agent_logs
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY agent_type;

-- 查询总 token 使用量
SELECT
  DATE(created_at) as date,
  SUM(tokens_used) as total_tokens,
  COUNT(*) as total_calls
FROM agent_logs
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY date DESC;

行级安全策略(RLS)

启用 RLS

sql
-- 启用所有表的 RLS
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
ALTER TABLE outlines ENABLE ROW LEVEL SECURITY;
ALTER TABLE sections ENABLE ROW LEVEL SECURITY;
ALTER TABLE optimizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE images ENABLE ROW LEVEL SECURITY;
ALTER TABLE agent_logs ENABLE ROW LEVEL SECURITY;

创建策略

projects 表策略

sql
-- 用户只能查看自己的项目
CREATE POLICY "Users can view their own projects"
  ON projects FOR SELECT
  USING (auth.uid() = user_id);

-- 用户只能创建自己的项目
CREATE POLICY "Users can create their own projects"
  ON projects FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- 用户只能更新自己的项目
CREATE POLICY "Users can update their own projects"
  ON projects FOR UPDATE
  USING (auth.uid() = user_id);

-- 用户只能删除自己的项目
CREATE POLICY "Users can delete their own projects"
  ON projects FOR DELETE
  USING (auth.uid() = user_id);

其他表策略(通过 project 关联):

sql
-- outlines 表
CREATE POLICY "Users can view outlines of their projects"
  ON outlines FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM projects
      WHERE projects.id = outlines.project_id
      AND projects.user_id = auth.uid()
    )
  );

-- 类似地为其他表创建策略...

RLS 的好处

  • ✅ 数据库级别的安全控制
  • ✅ 无需在应用层做权限检查
  • ✅ 防止 SQL 注入和越权访问

触发器(Triggers)

自动更新 updated_at

sql
-- 创建触发器函数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 为 projects 表创建触发器
CREATE TRIGGER update_projects_updated_at
  BEFORE UPDATE ON projects
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();

-- 为 sections 表创建触发器
CREATE TRIGGER update_sections_updated_at
  BEFORE UPDATE ON sections
  FOR EACH ROW
  EXECUTE FUNCTION update_updated_at_column();

自动计算字数

sql
-- 创建字数计算函数
CREATE OR REPLACE FUNCTION calculate_word_count(text_content TEXT)
RETURNS INTEGER AS $$
DECLARE
  clean_text TEXT;
  chinese_count INTEGER;
  english_count INTEGER;
BEGIN
  -- 移除 Markdown 语法
  clean_text := regexp_replace(text_content, '[#*`_\[\]]', '', 'g');
  clean_text := trim(clean_text);

  -- 计算中文字符数
  chinese_count := length(regexp_replace(clean_text, '[^\u4e00-\u9fa5]', '', 'g'));

  -- 计算英文单词数
  english_count := array_length(
    regexp_split_to_array(
      regexp_replace(clean_text, '[\u4e00-\u9fa5]', '', 'g'),
      '\s+'
    ),
    1
  );

  RETURN COALESCE(chinese_count, 0) + COALESCE(english_count, 0);
END;
$$ LANGUAGE plpgsql;

-- 创建触发器:自动计算字数
CREATE OR REPLACE FUNCTION update_section_word_count()
RETURNS TRIGGER AS $$
BEGIN
  NEW.word_count := calculate_word_count(NEW.content);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER calculate_section_word_count
  BEFORE INSERT OR UPDATE OF content ON sections
  FOR EACH ROW
  EXECUTE FUNCTION update_section_word_count();

视图(Views)

项目统计视图

sql
CREATE OR REPLACE VIEW project_statistics AS
SELECT
  p.id as project_id,
  p.title,
  p.status,
  COUNT(s.id) as section_count,
  SUM(s.word_count) as total_words,
  COUNT(CASE WHEN s.status = 'completed' THEN 1 END) as completed_sections,
  p.word_count_target,
  ROUND(
    (SUM(s.word_count)::NUMERIC / NULLIF(p.word_count_target, 0)) * 100,
    2
  ) as completion_percentage,
  p.created_at,
  p.updated_at
FROM projects p
LEFT JOIN sections s ON p.id = s.project_id
GROUP BY p.id;

使用示例

sql
-- 查询某个用户的所有项目统计
SELECT * FROM project_statistics
WHERE project_id IN (
  SELECT id FROM projects WHERE user_id = 'user-uuid'
)
ORDER BY updated_at DESC;

常用查询示例

1. 获取项目的完整信息

sql
SELECT
  p.*,
  o.content as outline,
  json_agg(
    json_build_object(
      'id', s.id,
      'heading', s.heading,
      'content', s.content,
      'word_count', s.word_count,
      'status', s.status,
      'order_index', s.order_index
    ) ORDER BY s.order_index
  ) as sections
FROM projects p
LEFT JOIN outlines o ON p.id = o.project_id
LEFT JOIN sections s ON p.id = s.project_id
WHERE p.id = 'project-uuid'
GROUP BY p.id, o.content;

2. 统计用户的项目数据

sql
SELECT
  COUNT(*) as total_projects,
  COUNT(CASE WHEN status = 'completed' THEN 1 END) as completed_projects,
  SUM((
    SELECT SUM(word_count)
    FROM sections
    WHERE project_id = projects.id
  )) as total_words_written
FROM projects
WHERE user_id = 'user-uuid';

3. 查询最近的 Agent 执行记录

sql
SELECT
  agent_type,
  status,
  duration_ms,
  tokens_used,
  created_at
FROM agent_logs
WHERE project_id = 'project-uuid'
ORDER BY created_at DESC
LIMIT 20;

面试要点总结

数据库设计亮点

1. JSONB 的使用

plain
"我使用 JSONB 存储大纲和优化建议,因为这些数据结构是动态的。
JSONB 比 JSON 性能更好,支持索引,而且 PostgreSQL 提供了
丰富的 JSONB 操作函数。"
2. RLS 安全策略
plain
"我使用 Supabase 的 RLS 功能,在数据库层面实现权限控制。
这样即使 API 被绕过,用户也无法访问其他人的数据,
安全性比应用层控制更高。"
3. 触发器自动化
plain
"我用触发器自动更新 updated_at 和计算字数,避免应用层
每次都要手动设置,减少了代码量和出错概率。"
4. 索引优化
plain
"我为高频查询创建了索引,特别是复合索引 (user_id, status)
和 GIN 索引用于 JSONB 查询,查询性能提升了 10 倍以上。"
5. 外键级联删除
plain
"我使用 ON DELETE CASCADE,当用户或项目被删除时,
相关的所有数据自动清理,保证数据一致性,不会产生孤儿记录。"