geo/docs/4-数据库/01-GEO平台数据库设计-v1.0.md

26 KiB
Raw Blame History

GEO 平台数据库设计文档

文档版本: v1.0 创建日期: 2026-05-01 依据文档: 技术详细设计文档


目录

  1. 数据库选型
  2. 数据库设计
  3. 表结构定义
  4. 索引设计
  5. 初始化脚本
  6. 数据迁移
  7. 备份策略

1. 数据库选型

1.1 主数据库: PostgreSQL

属性 选择 理由
版本 PostgreSQL 15+ JSONB支持、异步能力、成熟稳定
部署 Docker Compose 简化部署
连接池 asyncpg 异步高并发

选择理由:

  1. JSONB支持: 品牌别名、平台列表等半结构化数据存储
  2. 异步驱动: asyncpg + SQLAlchemy 2.0 完美支持异步
  3. 全文搜索: 支持中文分词 (可选扩展)
  4. 可靠性: 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 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

文档状态: 待评审 审批人: 技术负责人