You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

10 KiB

剧本表结构创建

变更日期:2026-02-03
变更类型:数据库迁移
影响范围:后端数据库、剧本管理功能


变更概述

创建剧本管理相关的数据库表结构,支持剧本文件上传、解析和元素标签管理。

变更内容

1. 新增表结构

1.1 screenplays 表

剧本主表,存储剧本基本信息和内容。

核心字段

字段名 类型 说明
screenplay_id UUID 主键(UUID v7)
project_id UUID 所属项目 ID
name TEXT 剧本名称
type SMALLINT 剧本类型:1=file, 2=manual
file_url TEXT 文件 URL(type=1 时)
file_name TEXT 原始文件名
file_size BIGINT 文件大小(字节)
mime_type TEXT 文件 MIME 类型
content TEXT 剧本文本内容(Markdown)
word_count INTEGER 字数统计
parsing_status SMALLINT 解析状态:0=idle, 1=pending, 2=parsing, 3=completed, 4=failed
parsing_error TEXT 解析错误信息
parsed_at TIMESTAMPTZ 解析完成时间
display_order INTEGER 显示顺序
created_at TIMESTAMPTZ 创建时间
updated_at TIMESTAMPTZ 更新时间
deleted_at TIMESTAMPTZ 软删除时间

索引

  • idx_screenplays_project_id - 项目查询
  • idx_screenplays_type - 类型筛选
  • idx_screenplays_parsing_status - 解析状态查询(部分索引)
  • idx_screenplays_order - 排序查询
  • idx_screenplays_deleted - 软删除过滤(部分索引)
  • idx_screenplays_name_trgm - 名称全文搜索(GIN)
  • idx_screenplays_content_trgm - 内容全文搜索(GIN)

约束

  • screenplays_type_check - 类型范围:1-2
  • screenplays_parsing_status_check - 解析状态范围:0-4
  • screenplays_word_count_check - 字数非负
  • screenplays_content_check - 内容完整性:file 类型必须有 file_url,manual 类型必须有 content

触发器

  • update_screenplays_updated_at - 自动更新 updated_at 字段

1.2 screenplay_tags 表

剧本元素标签表,存储剧本中的角色、场景、道具等元素及其标签。

核心字段

字段名 类型 说明
tag_id UUID 主键(UUID v7)
screenplay_id UUID 所属剧本 ID
element_type SMALLINT 元素类型:1=character, 2=scene, 3=prop, 4=sound, 5=other
element_name TEXT 元素名称(如"孙悟空"、"咖啡厅")
tag_label TEXT 标签名称(如"少年"、"白天")
description TEXT 元素描述
metadata JSONB 扩展字段
display_order INTEGER 显示顺序
created_at TIMESTAMPTZ 创建时间
updated_at TIMESTAMPTZ 更新时间

索引

  • idx_screenplay_tags_screenplay_id - 剧本查询
  • idx_screenplay_tags_element_type - 元素类型筛选
  • idx_screenplay_tags_element_name - 元素名称查询
  • idx_screenplay_tags_order - 排序查询
  • idx_screenplay_tags_metadata_gin - JSONB 字段查询(GIN)
  • idx_screenplay_tags_element_name_trgm - 元素名称全文搜索(GIN)
  • idx_screenplay_tags_tag_label_trgm - 标签名称全文搜索(GIN)

约束

  • screenplay_tags_element_type_check - 元素类型范围:1-5
  • screenplay_tags_unique - 唯一约束:(screenplay_id, element_type, element_name, tag_label)

触发器

  • update_screenplay_tags_updated_at - 自动更新 updated_at 字段

2. 设计原则

2.1 符合 jointo-tech-stack 规范

  • UUID v7:所有主键使用 UUID v7(应用层生成)
  • TIMESTAMPTZ:所有时间字段使用 TIMESTAMP(timezone=True)
  • SMALLINT 枚举:类型字段使用 SMALLINT 存储枚举值
  • 应用层外键:无物理外键约束,应用层保证引用完整性
  • 索引策略:合理使用 B-Tree、GIN、部分索引
  • 触发器:自动更新 updated_at 字段

2.2 文件解析状态管理

支持完整的文件解析生命周期:

idle (0) → pending (1) → parsing (2) → completed (3)
                                    ↘ failed (4)
  • idle:未解析(TXT/Markdown 直接读取后跳过此状态)
  • pending:待解析(任务已提交,等待 Celery Worker 处理)
  • parsing:解析中(Worker 正在处理)
  • completed:解析完成(content 字段已填充)
  • failed:解析失败(parsing_error 字段记录错误信息)

2.3 多种剧本类型支持

  • type=1 (file):上传文件类型

    • 支持格式:TXT、Markdown、DOCX、PDF、RTF、DOC
    • 必须字段:file_url, file_name, mime_type
    • 解析后填充:content, word_count
  • type=2 (manual):手动输入类型

    • 用户直接输入剧本文本
    • 必须字段:content
    • 自动计算:word_count

2.4 全文搜索支持

使用 PostgreSQL 的 pg_trgm 扩展实现全文搜索:

  • screenplays.name - 剧本名称搜索
  • screenplays.content - 剧本内容搜索
  • screenplay_tags.element_name - 元素名称搜索
  • screenplay_tags.tag_label - 标签名称搜索

3. 迁移文件

文件路径server/alembic/versions/20260203_1200_create_screenplays_tables.py

Revision ID20260203_1200

依赖20260203_1100 (create_storyboard_resources_tables)

使用示例

1. 创建文件类型剧本

from uuid import uuid7
from datetime import datetime, UTC

screenplay = {
    'screenplay_id': uuid7(),
    'project_id': project_id,
    'name': '第一集剧本',
    'type': 1,  # file
    'file_url': 'https://storage.jointo.ai/screenplays/1/abc123.pdf',
    'file_name': '第一集剧本.pdf',
    'file_size': 1024000,
    'mime_type': 'application/pdf',
    'parsing_status': 1,  # pending
    'display_order': 0,
    'created_at': datetime.now(UTC),
    'updated_at': datetime.now(UTC)
}

2. 更新解析状态

# 解析完成
await screenplay_repo.update(screenplay_id, {
    'content': '# 第一场 咖啡厅 下午\n\n张三坐在咖啡厅里...',
    'word_count': 5000,
    'parsing_status': 3,  # completed
    'parsed_at': datetime.now(UTC)
})

# 解析失败
await screenplay_repo.update(screenplay_id, {
    'parsing_status': 4,  # failed
    'parsing_error': 'PDF 文件损坏,无法解析'
})

3. 创建剧本元素标签

# 角色标签
character_tag = {
    'tag_id': uuid7(),
    'screenplay_id': screenplay_id,
    'element_type': 1,  # character
    'element_name': '孙悟空',
    'tag_label': '少年',
    'description': '主角,活泼好动',
    'metadata': {
        'appearance': '金色头发,红色衣服',
        'personality': '勇敢、正义',
        'age': '16'
    },
    'display_order': 0,
    'created_at': datetime.now(UTC),
    'updated_at': datetime.now(UTC)
}

# 场景标签
scene_tag = {
    'tag_id': uuid7(),
    'screenplay_id': screenplay_id,
    'element_type': 2,  # scene
    'element_name': '咖啡厅',
    'tag_label': '白天',
    'description': '温馨的咖啡厅',
    'metadata': {
        'location_type': 'indoor',
        'lighting': 'natural',
        'atmosphere': 'cozy'
    },
    'display_order': 1,
    'created_at': datetime.now(UTC),
    'updated_at': datetime.now(UTC)
}

4. 全文搜索

-- 搜索剧本名称
SELECT * FROM screenplays
WHERE name % '第一集'
ORDER BY similarity(name, '第一集') DESC;

-- 搜索剧本内容
SELECT * FROM screenplays
WHERE content % '咖啡厅'
ORDER BY similarity(content, '咖啡厅') DESC;

-- 搜索元素名称
SELECT * FROM screenplay_tags
WHERE element_name % '孙悟空'
ORDER BY similarity(element_name, '孙悟空') DESC;

后续工作

1. 创建 Model 类

  • server/app/models/screenplay.py - Screenplay 模型
  • server/app/models/screenplay_tag.py - ScreenplayTag 模型

2. 创建 Repository 类

  • server/app/repositories/screenplay_repository.py
  • server/app/repositories/screenplay_tag_repository.py

3. 创建 Schema 类

  • server/app/schemas/screenplay.py - 请求/响应 Schema

4. 完善 Service 类

  • 补充 ScreenplayFileParserService 的数据库操作
  • 创建 ScreenplayService - 剧本管理服务
  • 创建 ScreenplayTagService - 标签管理服务

5. 创建 API 路由

  • server/app/api/v1/screenplays.py - 剧本 CRUD API
  • server/app/api/v1/screenplay_tags.py - 标签 CRUD API

6. 创建 Celery 任务

  • server/app/tasks/screenplay_tasks.py - 文件解析异步任务

测试建议

1. 数据库迁移测试

# 升级
docker exec jointo-server-app alembic upgrade head

# 验证表结构
docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\d screenplays"
docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\d screenplay_tags"

# 降级测试
docker exec jointo-server-app alembic downgrade -1
docker exec jointo-server-app alembic upgrade head

2. 约束测试

-- 测试类型约束
INSERT INTO screenplays (screenplay_id, project_id, name, type)
VALUES (gen_random_uuid(), gen_random_uuid(), 'Test', 3);  -- 应失败

-- 测试内容完整性约束
INSERT INTO screenplays (screenplay_id, project_id, name, type)
VALUES (gen_random_uuid(), gen_random_uuid(), 'Test', 1);  -- 应失败(缺少 file_url)

-- 测试唯一约束
INSERT INTO screenplay_tags (tag_id, screenplay_id, element_type, element_name, tag_label)
VALUES 
  (gen_random_uuid(), 'xxx', 1, '孙悟空', '少年'),
  (gen_random_uuid(), 'xxx', 1, '孙悟空', '少年');  -- 应失败(重复)

3. 索引性能测试

-- 测试全文搜索性能
EXPLAIN ANALYZE
SELECT * FROM screenplays
WHERE name % '第一集';

-- 测试 JSONB 查询性能
EXPLAIN ANALYZE
SELECT * FROM screenplay_tags
WHERE metadata @> '{"age": "16"}';

相关文档


变更日期:2026-02-03
迁移文件20260203_1200_create_screenplays_tables.py
状态 已完成