26 KiB
26 KiB
GEO 平台数据库设计文档
文档版本: v1.0 创建日期: 2026-05-01 依据文档: 技术详细设计文档
目录
1. 数据库选型
1.1 主数据库: PostgreSQL
| 属性 | 选择 | 理由 |
|---|---|---|
| 版本 | PostgreSQL 15+ | JSONB支持、异步能力、成熟稳定 |
| 部署 | Docker Compose | 简化部署 |
| 连接池 | asyncpg | 异步高并发 |
选择理由:
- JSONB支持: 品牌别名、平台列表等半结构化数据存储
- 异步驱动: asyncpg + SQLAlchemy 2.0 完美支持异步
- 全文搜索: 支持中文分词 (可选扩展)
- 可靠性: ACID保证,数据一致性
1.2 缓存数据库: Redis
| 属性 | 选择 | 理由 |
|---|---|---|
| 版本 | Redis 7+ | 最新稳定版 |
| 用途 | 缓存/会话/队列 | 低延迟数据访问 |
使用场景:
- API响应缓存 (评分、统计)
- 会话存储
- 限流计数
- 任务队列
2. 数据库设计
2.1 命名规范
| 规范 | 示例 |
|---|---|
| 表名单词 | users, brands, citation_records |
| 字段名单词 | user_id, created_at |
| 索引命名 | idx_{table}_{column} |
| 主键 | id UUID |
| 时间戳 | created_at, updated_at |
2.2 ER图
┌─────────────────────────────────────────────────────────────────────────┐
│ USERS │
├─────────────────────────────────────────────────────────────────────────┤
│ id (PK, UUID) │
│ email (UNIQUE) │
│ password_hash │
│ name │
│ plan │
│ max_queries │
│ is_active │
│ email_verified │
│ verification_code │
│ verification_code_expires │
│ reset_token │
│ reset_token_expires │
│ created_at │
│ updated_at │
└─────────────────────────────────────────────────────────────────────────┘
│
│ 1:N
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ BRANDS │
├─────────────────────────────────────────────────────────────────────────┤
│ id (PK, UUID) │
│ user_id (FK → users.id) │
│ name (UNIQUE) │
│ aliases (JSONB) │
│ website │
│ industry │
│ platforms (JSONB) │
│ frequency │
│ status │
│ last_queried_at │
│ next_query_at │
│ created_at │
│ updated_at │
└─────────────────────────────────────────────────────────────────────────┘
│
│ 1:N │ 1:N │ 1:N
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ COMPETITORS │ │ CITATION_RECORD │ │ QUERY_TASKS │
├──────────────────┤ ├──────────────────┤ ├──────────────────┤
│ id (PK, UUID) │ │ id (PK, UUID) │ │ id (PK, UUID) │
│ brand_id (FK) │ │ brand_id (FK) │ │ brand_id (FK) │
│ name │ │ platform │ │ platform │
│ aliases (JSONB) │ │ cited │ │ status │
│ created_at │ │ citation_text │ │ error_message │
└──────────────────┘ │ citation_pos │ │ scheduled_at │
│ sentiment │ │ started_at │
│ competitor_brands│ │ completed_at │
│ raw_response │ └──────────────────┘
│ confidence │
│ match_type │
│ queried_at │
└──────────────────┘
3. 表结构定义
3.1 users 用户表
-- 用户表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(100),
plan VARCHAR(20) DEFAULT 'free' CHECK (plan IN ('free', 'pro', 'enterprise')),
max_queries INTEGER DEFAULT 5,
is_active BOOLEAN DEFAULT TRUE,
email_verified BOOLEAN DEFAULT FALSE,
verification_code VARCHAR(6),
verification_code_expires TIMESTAMP,
reset_token VARCHAR(255),
reset_token_expires TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_plan ON users(plan);
CREATE INDEX idx_users_is_active ON users(is_active);
-- 注释
COMMENT ON TABLE users IS '用户表';
COMMENT ON COLUMN users.plan IS '套餐: free=免费, pro=专业版, enterprise=企业版';
COMMENT ON COLUMN users.max_queries IS '每日最大查询次数';
3.2 brands 品牌表
-- 品牌表
CREATE TABLE brands (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
aliases JSONB DEFAULT '[]',
website VARCHAR(500),
industry VARCHAR(50),
platforms JSONB NOT NULL DEFAULT '["wenxin", "kimi"]',
frequency VARCHAR(20) DEFAULT 'weekly' CHECK (frequency IN ('daily', 'weekly', 'monthly')),
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'paused', 'deleted')),
last_queried_at TIMESTAMP,
next_query_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_brand_name_per_user UNIQUE (user_id, name)
);
-- 索引
CREATE INDEX idx_brands_user_id ON brands(user_id);
CREATE INDEX idx_brands_status ON brands(status);
CREATE INDEX idx_brands_next_query ON brands(next_query_at);
CREATE INDEX idx_brands_name ON brands(name);
-- 注释
COMMENT ON TABLE brands IS '品牌表';
COMMENT ON COLUMN brands.aliases IS '品牌别名列表 JSON数组';
COMMENT ON COLUMN brands.platforms IS '监控的AI平台列表 JSON数组';
COMMENT ON COLUMN brands.frequency IS '查询频率: daily=每日, weekly=每周, monthly=每月';
COMMENT ON COLUMN brands.status IS '状态: active=活跃, paused=暂停, deleted=已删除';
3.3 competitors 竞品表
-- 竞品表
CREATE TABLE competitors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
aliases JSONB DEFAULT '[]',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX idx_competitors_brand_id ON competitors(brand_id);
-- 注释
COMMENT ON TABLE competitors IS '竞品表';
COMMENT ON COLUMN competitors.aliases IS '竞品别名列表 JSON数组';
3.4 citation_records 引用记录表
-- 引用记录表
CREATE TABLE citation_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
platform VARCHAR(50) NOT NULL,
cited BOOLEAN NOT NULL DEFAULT FALSE,
citation_text TEXT,
citation_position INTEGER,
sentiment VARCHAR(20) CHECK (sentiment IN ('positive', 'neutral', 'negative', NULL)),
competitor_brands JSONB DEFAULT '[]',
raw_response TEXT,
confidence FLOAT CHECK (confidence >= 0 AND confidence <= 1),
match_type VARCHAR(20) CHECK (match_type IN ('exact', 'alias', 'fuzzy', NULL)),
queried_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- 索引
CREATE INDEX idx_citations_brand_id ON citation_records(brand_id);
CREATE INDEX idx_citations_platform ON citation_records(platform);
CREATE INDEX idx_citations_queried_at ON citation_records(queried_at);
CREATE INDEX idx_citations_brand_platform ON citation_records(brand_id, platform);
CREATE INDEX idx_citations_brand_queried ON citation_records(brand_id, queried_at);
CREATE INDEX idx_citations_cited ON citation_records(brand_id, cited);
-- 注释
COMMENT ON TABLE citation_records IS '引用记录表';
COMMENT ON COLUMN citation_records.cited IS '是否被引用';
COMMENT ON COLUMN citation_records.citation_position IS '引用位置(1-based)';
COMMENT ON COLUMN citation_records.sentiment IS '情感倾向: positive=正面, neutral=中性, negative=负面';
COMMENT ON COLUMN citation_records.match_type IS '匹配类型: exact=精确, alias=别名, fuzzy=模糊';
3.5 query_tasks 查询任务表
-- 查询任务表
CREATE TABLE query_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
platform VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'success', 'failed')),
error_message TEXT,
scheduled_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP
);
-- 索引
CREATE INDEX idx_query_tasks_brand_id ON query_tasks(brand_id);
CREATE INDEX idx_query_tasks_status ON query_tasks(status);
CREATE INDEX idx_query_tasks_scheduled ON query_tasks(scheduled_at);
CREATE INDEX idx_query_tasks_pending ON query_tasks(status, scheduled_at) WHERE status = 'pending';
-- 注释
COMMENT ON TABLE query_tasks IS '查询任务表';
COMMENT ON COLUMN query_tasks.status IS '任务状态: pending=等待中, running=执行中, success=成功, failed=失败';
4. 索引设计
4.1 索引清单
| 表名 | 索引名 | 列 | 类型 | 用途 |
|---|---|---|---|---|
| users | idx_users_email | B-Tree | 登录查询 | |
| users | idx_users_plan | plan | B-Tree | 套餐统计 |
| brands | idx_brands_user_id | user_id | B-Tree | 用户品牌列表 |
| brands | idx_brands_status | status | B-Tree | 状态筛选 |
| brands | idx_brands_next_query | next_query_at | B-Tree | 调度查询 |
| competitors | idx_competitors_brand_id | brand_id | B-Tree | 竞品列表 |
| citation_records | idx_citations_brand_id | brand_id | B-Tree | 品牌记录 |
| citation_records | idx_citations_platform | platform | B-Tree | 平台筛选 |
| citation_records | idx_citations_queried_at | queried_at | B-Tree | 时间筛选 |
| citation_records | idx_citations_brand_platform | brand_id, platform | B-Tree | 组合查询 |
| query_tasks | idx_query_tasks_brand_id | brand_id | B-Tree | 任务查询 |
| query_tasks | idx_query_tasks_status | status | B-Tree | 状态筛选 |
| query_tasks | idx_query_tasks_pending | status, scheduled_at | B-Tree | 调度查询 |
4.2 复合索引设计
-- 品牌+平台+时间 复合索引 (高频查询)
CREATE INDEX idx_citations_brand_platform_time
ON citation_records(brand_id, platform, queried_at DESC);
-- 品牌+是否引用 索引 (统计查询)
CREATE INDEX idx_citations_cited_flag
ON citation_records(brand_id, cited) WHERE cited = TRUE;
-- 待执行任务索引 (调度器查询)
CREATE INDEX idx_query_tasks_ready
ON query_tasks(status, scheduled_at)
WHERE status IN ('pending', 'running');
5. 初始化脚本
5.1 完整建库建表脚本
-- GEO 平台数据库初始化脚本
-- 版本: v1.0
-- 日期: 2026-05-01
-- 创建数据库 (如需要)
-- CREATE DATABASE geo_platform;
-- 连接到目标数据库后执行以下脚本
-- ============================================
-- 1. 扩展启用
-- ============================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================
-- 2. 用户表
-- ============================================
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(100),
plan VARCHAR(20) DEFAULT 'free' CHECK (plan IN ('free', 'pro', 'enterprise')),
max_queries INTEGER DEFAULT 5,
is_active BOOLEAN DEFAULT TRUE,
email_verified BOOLEAN DEFAULT FALSE,
verification_code VARCHAR(6),
verification_code_expires TIMESTAMP,
reset_token VARCHAR(255),
reset_token_expires TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_plan ON users(plan);
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
-- ============================================
-- 3. 品牌表
-- ============================================
CREATE TABLE IF NOT EXISTS brands (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
aliases JSONB DEFAULT '[]',
website VARCHAR(500),
industry VARCHAR(50),
platforms JSONB NOT NULL DEFAULT '["wenxin", "kimi"]',
frequency VARCHAR(20) DEFAULT 'weekly' CHECK (frequency IN ('daily', 'weekly', 'monthly')),
status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'paused', 'deleted')),
last_queried_at TIMESTAMP,
next_query_at TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_brand_name_per_user UNIQUE (user_id, name)
);
CREATE INDEX IF NOT EXISTS idx_brands_user_id ON brands(user_id);
CREATE INDEX IF NOT EXISTS idx_brands_status ON brands(status);
CREATE INDEX IF NOT EXISTS idx_brands_next_query ON brands(next_query_at);
CREATE INDEX IF NOT EXISTS idx_brands_name ON brands(name);
-- ============================================
-- 4. 竞品表
-- ============================================
CREATE TABLE IF NOT EXISTS competitors (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL,
aliases JSONB DEFAULT '[]',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_competitors_brand_id ON competitors(brand_id);
-- ============================================
-- 5. 引用记录表
-- ============================================
CREATE TABLE IF NOT EXISTS citation_records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
platform VARCHAR(50) NOT NULL,
cited BOOLEAN NOT NULL DEFAULT FALSE,
citation_text TEXT,
citation_position INTEGER,
sentiment VARCHAR(20) CHECK (sentiment IN ('positive', 'neutral', 'negative', NULL)),
competitor_brands JSONB DEFAULT '[]',
raw_response TEXT,
confidence FLOAT CHECK (confidence >= 0 AND confidence <= 1),
match_type VARCHAR(20) CHECK (match_type IN ('exact', 'alias', 'fuzzy', NULL)),
queried_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_citations_brand_id ON citation_records(brand_id);
CREATE INDEX IF NOT EXISTS idx_citations_platform ON citation_records(platform);
CREATE INDEX IF NOT EXISTS idx_citations_queried_at ON citation_records(queried_at);
CREATE INDEX IF NOT EXISTS idx_citations_brand_platform ON citation_records(brand_id, platform);
CREATE INDEX IF NOT EXISTS idx_citations_brand_queried ON citation_records(brand_id, queried_at DESC);
-- ============================================
-- 6. 查询任务表
-- ============================================
CREATE TABLE IF NOT EXISTS query_tasks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
brand_id UUID NOT NULL REFERENCES brands(id) ON DELETE CASCADE,
platform VARCHAR(50) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'running', 'success', 'failed')),
error_message TEXT,
scheduled_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_query_tasks_brand_id ON query_tasks(brand_id);
CREATE INDEX IF NOT EXISTS idx_query_tasks_status ON query_tasks(status);
CREATE INDEX IF NOT EXISTS idx_query_tasks_scheduled ON query_tasks(scheduled_at);
CREATE INDEX IF NOT EXISTS idx_query_tasks_pending ON query_tasks(status, scheduled_at) WHERE status = 'pending';
-- ============================================
-- 7. 触发器: updated_at 自动更新
-- ============================================
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_users_updated_at
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
CREATE TRIGGER update_brands_updated_at
BEFORE UPDATE ON brands
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- ============================================
-- 8. 视图: 品牌评分汇总
-- ============================================
CREATE OR REPLACE VIEW brand_score_summary AS
SELECT
b.id AS brand_id,
b.name AS brand_name,
b.user_id,
COUNT(cr.id) AS total_queries,
SUM(CASE WHEN cr.cited THEN 1 ELSE 0 END) AS total_cited,
ROUND(
SUM(CASE WHEN cr.cited THEN 1 ELSE 0 END)::NUMERIC /
NULLIF(COUNT(cr.id), 0) * 100,
2
) AS citation_rate,
AVG(cr.confidence) AS avg_confidence
FROM brands b
LEFT JOIN citation_records cr ON b.id = cr.brand_id
WHERE b.status = 'active'
GROUP BY b.id, b.name, b.user_id;
-- ============================================
-- 9. 视图: 平台统计
-- ============================================
CREATE OR REPLACE VIEW platform_stats AS
SELECT
cr.platform,
COUNT(*) AS total_queries,
SUM(CASE WHEN cr.cited THEN 1 ELSE 0 END) AS total_cited,
ROUND(
SUM(CASE WHEN cr.cited THEN 1 ELSE 0 END)::NUMERIC /
NULLIF(COUNT(*), 0) * 100,
2
) AS citation_rate
FROM citation_records cr
GROUP BY cr.platform;
-- ============================================
-- 10. 初始化数据
-- ============================================
-- 创建管理员用户 (密码: Admin123!)
-- 实际部署时请更换密码
INSERT INTO users (
id,
email,
password_hash,
name,
plan,
max_queries,
is_active,
email_verified,
is_admin
) VALUES (
'00000000-0000-0000-0000-000000000001',
'admin@geo-platform.com',
'$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.FQgL3aHf9gHqOK', -- Admin123!
'系统管理员',
'enterprise',
999999,
TRUE,
TRUE,
TRUE
) ON CONFLICT (email) DO NOTHING;
-- ============================================
-- 11. 权限设置 (如使用非postgres用户)
-- ============================================
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO geo_app;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO geo_app;
-- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO geo_app;
-- ============================================
-- 完成
-- ============================================
DO $$
BEGIN
RAISE NOTICE 'GEO Platform database initialized successfully!';
END $$;
5.2 测试数据脚本
-- ============================================
-- 测试数据脚本
-- 仅用于开发/测试环境
-- ============================================
-- 清理现有测试数据
DELETE FROM query_tasks WHERE brand_id IN (SELECT id FROM brands WHERE name LIKE '测试%');
DELETE FROM citation_records WHERE brand_id IN (SELECT id FROM brands WHERE name LIKE '测试%');
DELETE FROM competitors WHERE brand_id IN (SELECT id FROM brands WHERE name LIKE '测试%');
DELETE FROM brands WHERE name LIKE '测试%';
DELETE FROM users WHERE email LIKE 'test%';
-- 创建测试用户
INSERT INTO users (id, email, password_hash, name, plan, max_queries, is_active, email_verified)
VALUES (
'11111111-1111-1111-1111-111111111111',
'test@example.com',
'$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.FQgL3aHf9gHqOK', -- Password123!
'测试用户',
'pro',
100,
TRUE,
TRUE
);
-- 创建测试品牌
INSERT INTO brands (id, user_id, name, aliases, platforms, frequency, status)
VALUES (
'22222222-2222-2222-2222-222222222222',
'11111111-1111-1111-1111-111111111111',
'测试品牌A',
'["品牌A", "TestA"]'::jsonb,
'["wenxin", "kimi", "tongyi"]'::jsonb,
'daily',
'active'
);
-- 创建竞品
INSERT INTO competitors (brand_id, name, aliases)
VALUES
('22222222-2222-2222-2222-222222222222', '竞品X', '["竞品X", "JX"]'::jsonb),
('22222222-2222-2222-2222-222222222222', '竞品Y', '["竞品Y"]'::jsonb);
-- 创建测试引用记录
INSERT INTO citation_records (brand_id, platform, cited, citation_text, citation_position, sentiment, confidence, match_type)
VALUES
('22222222-2222-2222-2222-222222222222', 'wenxin', TRUE, '测试品牌A是一家领先的企业...', 1, 'positive', 0.95, 'exact'),
('22222222-2222-2222-2222-222222222222', 'wenxin', TRUE, '在AI领域,TestA表现突出...', 2, 'positive', 0.88, 'alias'),
('22222222-2222-2222-2222-222222222222', 'kimi', FALSE, '目前暂无相关数据...', NULL, NULL, NULL, NULL),
('22222222-2222-2222-2222-222222222222', 'tongyi', TRUE, '测试品牌A在行业中...', 1, 'neutral', 0.92, 'exact');
DO $$
BEGIN
RAISE NOTICE 'Test data inserted successfully!';
END $$;
6. 数据迁移
6.1 Alembic配置
alembic.ini 配置:
[alembic]
script_location = alembic
prepend_sys_path = .
version_path_separator = os
sqlalchemy.url = postgresql+asyncpg://postgres:postgres123@localhost:5432/geo_platform
[post_write_hooks]
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
6.2 迁移命令
# 进入后端目录
cd backend
# 生成迁移
alembic revision --autogenerate -m "initial migration"
# 执行迁移
alembic upgrade head
# 查看当前版本
alembic current
# 回滚
alembic downgrade -1
# 查看历史
alembic history
7. 备份策略
7.1 备份类型
| 类型 | 频率 | 保留 | 说明 |
|---|---|---|---|
| 全量备份 | 每日 | 30天 | 完整数据库快照 |
| WAL归档 | 实时 | 7天 | 增量恢复 |
| 表导出 | 每周 | 12周 | 关键表CSV导出 |
7.2 备份脚本
#!/bin/bash
# backup.sh - 数据库备份脚本
# 配置
BACKUP_DIR="/backups/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="geo_platform"
DB_USER="postgres"
DB_HOST="localhost"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 全量备份
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME -F c -f "$BACKUP_DIR/full_backup_$DATE.dump"
# 删除30天前的备份
find $BACKUP_DIR -name "full_backup_*.dump" -mtime +30 -delete
# 记录备份日志
echo "[$(date)] Backup completed: full_backup_$DATE.dump" >> /var/log/backup.log
7.3 恢复流程
# 停止服务
docker-compose stop backend
# 恢复数据库
pg_restore -h localhost -U postgres -d geo_platform -c /backups/postgres/full_backup_20260501.dump
# 启动服务
docker-compose start backend
文档状态: 待评审 审批人: 技术负责人