use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { // 1. ALTER articles 表:新增字段 manager .alter_table( Table::alter() .table(Article::Table) .add_column( ColumnDef::new(Article::Status) .string_len(20) .not_null() .default("draft"), ) .add_column(ColumnDef::new(Article::Slug).string_len(200).null()) .add_column( ColumnDef::new(Article::ContentType) .string_len(20) .not_null() .default("rich_text"), ) .add_column(ColumnDef::new(Article::ReviewedBy).uuid().null()) .add_column( ColumnDef::new(Article::ReviewedAt) .timestamp_with_time_zone() .null(), ) .add_column(ColumnDef::new(Article::ReviewNote).text().null()) .add_column( ColumnDef::new(Article::ViewCount) .integer() .not_null() .default(0), ) .add_column( ColumnDef::new(Article::SortOrder) .integer() .not_null() .default(0), ) .add_column(ColumnDef::new(Article::CategoryId).uuid().null()) .to_owned(), ) .await?; // 将已有 published_at 不为 null 的记录状态设为 published manager .get_connection() .execute_unprepared( "UPDATE article SET status = 'published' WHERE published_at IS NOT NULL AND deleted_at IS NULL", ) .await?; // 2. 创建 article_category 表 manager .create_table( Table::create() .table(ArticleCategory::Table) .col( ColumnDef::new(ArticleCategory::Id) .uuid() .not_null() .primary_key(), ) .col(ColumnDef::new(ArticleCategory::TenantId).uuid().not_null()) .col( ColumnDef::new(ArticleCategory::Name) .string_len(100) .not_null(), ) .col(ColumnDef::new(ArticleCategory::Slug).string_len(100).null()) .col(ColumnDef::new(ArticleCategory::ParentId).uuid().null()) .col(ColumnDef::new(ArticleCategory::Description).text().null()) .col( ColumnDef::new(ArticleCategory::SortOrder) .integer() .not_null() .default(0), ) .col( ColumnDef::new(ArticleCategory::CreatedAt) .timestamp_with_time_zone() .not_null() .default(Expr::current_timestamp()), ) .col( ColumnDef::new(ArticleCategory::UpdatedAt) .timestamp_with_time_zone() .not_null() .default(Expr::current_timestamp()), ) .col(ColumnDef::new(ArticleCategory::CreatedBy).uuid().null()) .col(ColumnDef::new(ArticleCategory::UpdatedBy).uuid().null()) .col( ColumnDef::new(ArticleCategory::DeletedAt) .timestamp_with_time_zone() .null(), ) .col( ColumnDef::new(ArticleCategory::Version) .integer() .not_null() .default(1), ) .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx_article_category_tenant") .table(ArticleCategory::Table) .col(ArticleCategory::TenantId) .to_owned(), ) .await?; // 3. 创建 article_tag 表 manager .create_table( Table::create() .table(ArticleTag::Table) .col( ColumnDef::new(ArticleTag::Id) .uuid() .not_null() .primary_key(), ) .col(ColumnDef::new(ArticleTag::TenantId).uuid().not_null()) .col(ColumnDef::new(ArticleTag::Name).string_len(50).not_null()) .col( ColumnDef::new(ArticleTag::CreatedAt) .timestamp_with_time_zone() .not_null() .default(Expr::current_timestamp()), ) .col( ColumnDef::new(ArticleTag::UpdatedAt) .timestamp_with_time_zone() .not_null() .default(Expr::current_timestamp()), ) .col(ColumnDef::new(ArticleTag::CreatedBy).uuid().null()) .col(ColumnDef::new(ArticleTag::UpdatedBy).uuid().null()) .col( ColumnDef::new(ArticleTag::DeletedAt) .timestamp_with_time_zone() .null(), ) .col( ColumnDef::new(ArticleTag::Version) .integer() .not_null() .default(1), ) .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx_article_tag_tenant") .table(ArticleTag::Table) .col(ArticleTag::TenantId) .to_owned(), ) .await?; // 4. 创建 article_article_tag 关联表(复合主键) manager .create_table( Table::create() .table(ArticleArticleTag::Table) .col( ColumnDef::new(ArticleArticleTag::ArticleId) .uuid() .not_null(), ) .col(ColumnDef::new(ArticleArticleTag::TagId).uuid().not_null()) .primary_key( Index::create() .col(ArticleArticleTag::ArticleId) .col(ArticleArticleTag::TagId), ) .to_owned(), ) .await?; // 5. 创建 article_revision 版本历史表 manager .create_table( Table::create() .table(ArticleRevision::Table) .col( ColumnDef::new(ArticleRevision::Id) .uuid() .not_null() .primary_key(), ) .col(ColumnDef::new(ArticleRevision::TenantId).uuid().not_null()) .col(ColumnDef::new(ArticleRevision::ArticleId).uuid().not_null()) .col( ColumnDef::new(ArticleRevision::RevisionNumber) .integer() .not_null(), ) .col( ColumnDef::new(ArticleRevision::Title) .string_len(255) .not_null(), ) .col(ColumnDef::new(ArticleRevision::Content).text().not_null()) .col(ColumnDef::new(ArticleRevision::Summary).text().null()) .col(ColumnDef::new(ArticleRevision::CreatedBy).uuid().null()) .col( ColumnDef::new(ArticleRevision::CreatedAt) .timestamp_with_time_zone() .not_null() .default(Expr::current_timestamp()), ) .to_owned(), ) .await?; manager .create_index( Index::create() .name("idx_article_revision_article") .table(ArticleRevision::Table) .col(ArticleRevision::ArticleId) .to_owned(), ) .await?; Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(ArticleRevision::Table).to_owned()) .await?; manager .drop_table(Table::drop().table(ArticleArticleTag::Table).to_owned()) .await?; manager .drop_table(Table::drop().table(ArticleTag::Table).to_owned()) .await?; manager .drop_table(Table::drop().table(ArticleCategory::Table).to_owned()) .await?; manager .alter_table( Table::alter() .table(Article::Table) .drop_column(Article::Status) .drop_column(Article::Slug) .drop_column(Article::ContentType) .drop_column(Article::ReviewedBy) .drop_column(Article::ReviewedAt) .drop_column(Article::ReviewNote) .drop_column(Article::ViewCount) .drop_column(Article::SortOrder) .drop_column(Article::CategoryId) .to_owned(), ) .await?; Ok(()) } } #[derive(DeriveIden)] enum Article { Table, Status, Slug, ContentType, ReviewedBy, ReviewedAt, ReviewNote, ViewCount, SortOrder, CategoryId, } #[derive(DeriveIden)] enum ArticleCategory { Table, Id, TenantId, Name, Slug, ParentId, Description, SortOrder, CreatedAt, UpdatedAt, CreatedBy, UpdatedBy, DeletedAt, Version, } #[derive(DeriveIden)] enum ArticleTag { Table, Id, TenantId, Name, CreatedAt, UpdatedAt, CreatedBy, UpdatedBy, DeletedAt, Version, } #[derive(DeriveIden)] enum ArticleArticleTag { Table, ArticleId, TagId, } #[derive(DeriveIden)] enum ArticleRevision { Table, Id, TenantId, ArticleId, RevisionNumber, Title, Content, Summary, CreatedBy, CreatedAt, }