数据库概览
技术选型
Supabase (PostgreSQL 14)
选择理由:
- ✅ 基于 PostgreSQL,功能强大
- ✅ 内置认证系统(Auth)
- ✅ 实时订阅功能(Realtime)
- ✅ 向量存储支持(pgvector)
- ✅ 免费额度充足(500MB 数据库)
- ✅ 自动生成 REST API
表结构设计
1. users 表(用户表)
说明:使用 Supabase Auth,自动创建
-- Supabase 自动管理
-- auth.users 表包含:
-- - id (UUID)
-- - email (VARCHAR)
-- - created_at (TIMESTAMPTZ)
-- - updated_at (TIMESTAMPTZ)
关键点:
- Supabase Auth 自动处理用户注册、登录
- 不需要手动创建用户表
- 其他表通过
user_id关联到auth.users(id)
2. projects 表(项目表)
用途:存储用户创建的内容项目
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 生成的文章大纲
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):
{
"title": "文章标题",
"introduction": {
"hook": "开场白",
"thesis": "核心论点",
"wordCount": 200
},
"sections": [
{
"heading": "第一章节",
"points": ["要点1", "要点2"],
"wordCount": 500,
"tips": "写作建议"
}
],
"conclusion": {
"summary": "总结",
"callToAction": "行动号召",
"wordCount": 300
}
}
设计亮点:
- JSONB 存储:灵活的数据结构,无需预定义所有字段
- 版本控制:支持多个版本的大纲
- GIN 索引:支持 JSONB 内部字段的快速查询
- 数组类型:PostgreSQL 原生支持,高效存储关键词
面试要点:
"我使用 JSONB 而不是 JSON,因为 JSONB 是二进制格式,查询性能更好。
而且 PostgreSQL 的 GIN 索引可以对 JSONB 内部字段建立索引,
支持高效的复杂查询。"
4. sections 表(章节表)
用途:存储每个章节的具体内容
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 |
唯一约束说明:
UNIQUE(project_id, order_index)
确保同一项目内章节顺序不重复,防止数据错误。
5. optimizations 表(优化建议表)
用途:存储优化 Agent 的分析结果和建议
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):
{
"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 表(图片表)
用途:存储配图信息
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 调用的日志,用于调试和分析
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);
用途说明:
- 调试:快速定位问题
- 性能分析:统计平均执行时间
- 成本核算:统计 token 使用量
- 质量监控:分析成功率和失败原因
示例查询:
-- 查询平均执行时间
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
-- 启用所有表的 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 表策略:
-- 用户只能查看自己的项目
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 关联):
-- 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
-- 创建触发器函数
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();
自动计算字数
-- 创建字数计算函数
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)
项目统计视图
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;
使用示例:
-- 查询某个用户的所有项目统计
SELECT * FROM project_statistics
WHERE project_id IN (
SELECT id FROM projects WHERE user_id = 'user-uuid'
)
ORDER BY updated_at DESC;
常用查询示例
1. 获取项目的完整信息
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. 统计用户的项目数据
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 执行记录
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 的使用
"我使用 JSONB 存储大纲和优化建议,因为这些数据结构是动态的。
JSONB 比 JSON 性能更好,支持索引,而且 PostgreSQL 提供了
丰富的 JSONB 操作函数。"
2. RLS 安全策略
"我使用 Supabase 的 RLS 功能,在数据库层面实现权限控制。
这样即使 API 被绕过,用户也无法访问其他人的数据,
安全性比应用层控制更高。"
3. 触发器自动化
"我用触发器自动更新 updated_at 和计算字数,避免应用层
每次都要手动设置,减少了代码量和出错概率。"
4. 索引优化
"我为高频查询创建了索引,特别是复合索引 (user_id, status)
和 GIN 索引用于 JSONB 查询,查询性能提升了 10 倍以上。"
5. 外键级联删除
"我使用 ON DELETE CASCADE,当用户或项目被删除时,
相关的所有数据自动清理,保证数据一致性,不会产生孤儿记录。"