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, }