package repositories import ( "fmt" "log" "time" "github.com/niangaodev/art-code/config" "github.com/niangaodev/art-code/models" ) // MigrateToBigInt performs schema migration to BigInt timestamps func MigrateToBigInt() { tables := []string{ "posts", "users", "tags", "works", "snippets", "settings", "about_profiles", "partners", "testimonials", "inquiries", "email_suffixes", "access_logs", "user_access_logs", "operation_logs", "permissions", "roles", "work_tech_stack", "work_gallery", "post_tags", "post_history", "code_types", } for _, table := range tables { log.Printf("Migrating table: %s", table) // 1. Add deleted_at if not exists if !columnExists(table, "deleted_at") && table != "post_tags" { // post_tags doesn't need deleted_at in my previous plan? I added to all in SQL file? Yes. // Actually post_tags in SQL file I modified: `created_at` bigint. No `deleted_at`. // So skip deleted_at for post_tags. if table != "post_tags" { execSQL(fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `deleted_at` BIGINT NOT NULL DEFAULT 0", table)) } } // 2. Migrate created_at migrateColumn(table, "created_at") // 3. Migrate updated_at (if exists) if columnExists(table, "updated_at") { migrateColumn(table, "updated_at") } // 4. Migrate access_time (for user_access_logs) if table == "user_access_logs" && columnExists(table, "access_time") { migrateColumn(table, "access_time") } // 5. Remove 'date' from posts if exists if table == "posts" && columnExists(table, "date") { execSQL("ALTER TABLE `posts` DROP COLUMN `date`") // Drop index idx_date if exists? MySQL drops index on column drop usually. } } } func columnExists(tableName, colName string) bool { var count int64 err := config.DB.Raw(` SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? `, tableName, colName).Scan(&count).Error if err != nil { log.Printf("Error checking column %s.%s: %v", tableName, colName, err) return false } return count > 0 } func isBigInt(tableName, colName string) bool { var dataType string err := config.DB.Raw(` SELECT DATA_TYPE FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ? `, tableName, colName).Scan(&dataType).Error if err != nil { return false } return dataType == "bigint" || dataType == "int" } func migrateColumn(table, col string) { if !columnExists(table, col) { return } if isBigInt(table, col) { return // Already migrated } log.Printf("Converting %s.%s to BIGINT...", table, col) // Rename old oldCol := col + "_old_dt" execSQL(fmt.Sprintf("ALTER TABLE `%s` CHANGE `%s` `%s` DATETIME", table, col, oldCol)) // Ensure it's treated as datetime for rename // Add new execSQL(fmt.Sprintf("ALTER TABLE `%s` ADD COLUMN `%s` BIGINT NOT NULL DEFAULT 0", table, col)) // Copy and Convert execSQL(fmt.Sprintf("UPDATE `%s` SET `%s` = UNIX_TIMESTAMP(`%s`) WHERE `%s` IS NOT NULL", table, col, oldCol, oldCol)) // Drop old execSQL(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN `%s`", table, oldCol)) } func execSQL(query string) { err := config.DB.Exec(query).Error if err != nil { // Log but continue (might fail if column doesn't exist etc) log.Printf("SQL Error: %v | Query: %s", err, query) } } // MigratePostHistoryFields adds column_id and tag_ids to post_history if missing. func MigratePostHistoryFields() { if !columnExists("post_history", "column_id") { log.Printf("Adding post_history.column_id...") execSQL("ALTER TABLE `post_history` ADD COLUMN `column_id` INT UNSIGNED NULL AFTER `category_id`") } if !columnExists("post_history", "tag_ids") { log.Printf("Adding post_history.tag_ids...") execSQL("ALTER TABLE `post_history` ADD COLUMN `tag_ids` JSON NULL COMMENT '标签ID快照' AFTER `column_id`") } } func tableExists(tableName string) bool { var count int64 err := config.DB.Raw(` SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ? `, tableName).Scan(&count).Error if err != nil { log.Printf("Error checking table %s: %v", tableName, err) return false } return count > 0 } // MigratePostSnippets creates post_snippets junction table if missing. func MigratePostSnippets() { if tableExists("post_snippets") { return } log.Printf("Creating post_snippets table...") execSQL(`CREATE TABLE post_snippets ( post_id INT UNSIGNED NOT NULL COMMENT '文章ID', snippet_id BIGINT UNSIGNED NOT NULL COMMENT '代码片段ID', sort_order INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)', created_at BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)', PRIMARY KEY (post_id, snippet_id), INDEX idx_snippet_id (snippet_id), INDEX idx_sort_order (sort_order) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章代码片段关联表'`) } // MigratePostUserID adds user_id to posts if missing. func MigratePostUserID() { if columnExists("posts", "user_id") { return } log.Printf("Adding posts.user_id...") execSQL("ALTER TABLE `posts` ADD COLUMN `user_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建者用户ID' AFTER `is_published`") execSQL("ALTER TABLE `posts` ADD INDEX `idx_user_id` (`user_id`)") } // MigrateUserAvatar adds avatar to users if missing. func MigrateUserAvatar() { if columnExists("users", "avatar") { return } log.Printf("Adding users.avatar...") execSQL("ALTER TABLE `users` ADD COLUMN `avatar` VARCHAR(500) NULL DEFAULT NULL COMMENT '头像URL' AFTER `email`") } // MigrateUserProfileFields adds profile fields to users if missing. func MigrateUserProfileFields() { if !columnExists("users", "bio") { log.Printf("Adding users.bio...") execSQL("ALTER TABLE `users` ADD COLUMN `bio` TEXT NULL COMMENT '个人介绍' AFTER `avatar`") } if !columnExists("users", "phone") { log.Printf("Adding users.phone...") execSQL("ALTER TABLE `users` ADD COLUMN `phone` VARCHAR(20) NULL DEFAULT NULL COMMENT '手机号' AFTER `bio`") } if !columnExists("users", "wechat") { log.Printf("Adding users.wechat...") execSQL("ALTER TABLE `users` ADD COLUMN `wechat` VARCHAR(100) NULL DEFAULT NULL COMMENT '微信号' AFTER `phone`") } if !columnExists("users", "wechat_qrcode") { log.Printf("Adding users.wechat_qrcode...") execSQL("ALTER TABLE `users` ADD COLUMN `wechat_qrcode` VARCHAR(500) NULL DEFAULT NULL COMMENT '微信二维码图片URL' AFTER `wechat`") } } // MigrateUserWxOpenID adds wx_openid for mini-program silent login. func MigrateUserWxOpenID() { if columnExists("users", "wx_openid") { return } log.Printf("Adding users.wx_openid...") execSQL("ALTER TABLE `users` ADD COLUMN `wx_openid` VARCHAR(64) NULL DEFAULT NULL COMMENT '微信小程序openid' AFTER `wechat_qrcode`") execSQL("CREATE UNIQUE INDEX `idx_users_wx_openid` ON `users` (`wx_openid`)") } // MigrateVideoModule 创建视频模块表及作品视频字段 func MigrateVideoModule() { log.Printf("Migrating video module...") if !tableExists("video_categories") { execSQL(`CREATE TABLE video_categories ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, slug VARCHAR(100) NOT NULL, description TEXT NULL, sort_order INT UNSIGNED NOT NULL DEFAULT 0, deleted_at BIGINT NOT NULL DEFAULT 0, created_at BIGINT NOT NULL DEFAULT 0, updated_at BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE INDEX idx_slug (slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频分类表'`) } if !tableExists("video_albums") { execSQL(`CREATE TABLE video_albums ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, name VARCHAR(200) NOT NULL, description TEXT NULL, cover VARCHAR(500) NULL, category_id INT UNSIGNED NOT NULL DEFAULT 0, is_active TINYINT(1) NOT NULL DEFAULT 1, sort_order INT UNSIGNED NOT NULL DEFAULT 0, deleted_at BIGINT NOT NULL DEFAULT 0, created_at BIGINT NOT NULL DEFAULT 0, updated_at BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (id), INDEX idx_category_id (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频专辑表'`) } if !tableExists("videos") { execSQL(`CREATE TABLE videos ( id VARCHAR(50) NOT NULL, title VARCHAR(200) NOT NULL, description TEXT NULL, video_url VARCHAR(500) NOT NULL, cover VARCHAR(500) NULL, poster VARCHAR(500) NULL, category_id INT UNSIGNED NOT NULL DEFAULT 0, duration INT NOT NULL DEFAULT 0, is_published TINYINT(1) NOT NULL DEFAULT 1, deleted_at BIGINT NOT NULL DEFAULT 0, created_at BIGINT NOT NULL DEFAULT 0, updated_at BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (id), INDEX idx_category_id (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='视频表'`) } if !tableExists("album_videos") { execSQL(`CREATE TABLE album_videos ( album_id INT UNSIGNED NOT NULL, video_id VARCHAR(50) NOT NULL, sort_order INT UNSIGNED NOT NULL DEFAULT 0, created_at BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (album_id, video_id), INDEX idx_video_id (video_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='专辑视频关联表'`) } if !columnExists("works", "hero_video") { execSQL("ALTER TABLE `works` ADD COLUMN `hero_video` VARCHAR(500) NULL DEFAULT NULL COMMENT '演示视频URL' AFTER `hero_img`") } if !columnExists("works", "video_id") { execSQL("ALTER TABLE `works` ADD COLUMN `video_id` VARCHAR(50) NULL DEFAULT NULL COMMENT '关联视频库ID' AFTER `hero_video`") } // 插入 videos 权限(若不存在) now := time.Now().Unix() actions := []string{"read", "create", "update", "delete"} for _, action := range actions { var count int64 config.DB.Raw("SELECT COUNT(*) FROM permissions WHERE resource = ? AND action = ? AND deleted_at = 0", "videos", action).Scan(&count) if count == 0 { execSQL(fmt.Sprintf("INSERT INTO permissions (name, resource, action, deleted_at, created_at, updated_at) VALUES ('videos:%s', 'videos', '%s', 0, %d, %d)", action, action, now, now)) } } // 为 role_id=1 的管理员角色授予 videos 权限 type permRow struct{ ID uint } var rows []permRow config.DB.Raw("SELECT id FROM permissions WHERE resource = 'videos' AND deleted_at = 0").Scan(&rows) for _, row := range rows { var count int64 config.DB.Raw("SELECT COUNT(*) FROM role_permissions WHERE role_id = 1 AND permission_id = ?", row.ID).Scan(&count) if count == 0 { execSQL(fmt.Sprintf("INSERT INTO role_permissions (role_id, permission_id) VALUES (1, %d)", row.ID)) } } } // MigratePptTemplates 创建 PPT 模板表并补种系统默认模板 func MigratePptTemplates() { log.Printf("Migrating ppt templates...") if !tableExists("ppt_templates") { execSQL(`CREATE TABLE ppt_templates ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '模板ID', name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板显示名称', slug VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'URL唯一标识(系统默认为default)', description TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '模板描述', config LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'PPT样式配置JSON(配色/动画/切换/UI)', is_system TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否系统内置模板(0否 1是,不可删除)', is_default TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否演示默认模板(0否 1是)', sort_order INT NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)', is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(0否 1是)', deleted_at BIGINT NOT NULL DEFAULT 0 COMMENT '软删除时间戳', created_at BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)', updated_at BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间(Unix秒)', PRIMARY KEY (id), UNIQUE INDEX idx_slug (slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PPT模板表'`) } else { ensurePptTemplateColumnComments() } EnsureSystemPptTemplate() } // MigrateTemplates 创建 templates 表并种入内置模板(classic / ubuntu-terminal) func MigrateTemplates() { log.Printf("Migrating templates...") if !tableExists("templates") { execSQL(`CREATE TABLE templates ( id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '模板ID', slug VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '唯一标识(classic/ubuntu-terminal/自定义slug)', name VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板显示名称', type VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'custom' COMMENT '类型(builtin内置/custom自定义)', description TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '模板描述', manifest LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'manifest.json 内容(custom)', layout_json LONGTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT 'layout.json 内容(custom)', skin_css_path VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '皮肤CSS相对路径(/uploads/templates/{slug}/skin.css)', is_system TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否系统内置模板(0否 1是,不可删除)', is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(0否 1是)', sort_order INT NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)', deleted_at BIGINT NOT NULL DEFAULT 0 COMMENT '软删除时间戳', created_at BIGINT NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)', updated_at BIGINT NOT NULL DEFAULT 0 COMMENT '更新时间(Unix秒)', PRIMARY KEY (id), UNIQUE INDEX idx_slug (slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='前端页面模板表'`) } ensureBuiltinTemplate(BuiltinTemplateClassic, "经典", "系统内置经典布局", 0) ensureBuiltinTemplate(BuiltinTemplateTerminal, "Ubuntu 终端", "系统内置终端风格(ls/search/cat 命令交互)", 1) ensureBuiltinTemplate(BuiltinTemplatePixel, "像素", "系统内置像素风(8-bit 游戏机界面)", 2) ensureBuiltinTemplate(BuiltinTemplateMagazine, "杂志", "系统内置杂志风(衬线多栏编辑排版)", 3) ensureBuiltinTemplate(BuiltinTemplateNewspaper, "报纸", "系统内置报纸风(报头多栏纸感排版)", 4) ensureBuiltinTemplate(BuiltinTemplateBento, "Bento 宫格", "系统内置 Bento 宫格风(圆角几何色块)", 5) ensureBuiltinTemplate(BuiltinTemplateGlass, "玻璃拟态", "系统内置玻璃拟态(毛玻璃光感)", 6) ensureBuiltinTemplate(BuiltinTemplateRetro, "复古印刷", "系统内置复古印刷(旧印刷烫金质感)", 7) ensureBuiltinTemplate(BuiltinTemplateGuofeng, "国风水墨", "系统内置国风水墨(宣纸印章山水)", 8) // 种入 site_template 默认配置 if s, err := GetSettingByKey(SettingKeySiteTemplate); err == nil && s == nil { _ = CreateSetting(&models.Setting{ KeyName: SettingKeySiteTemplate, Value: DefaultSiteTemplateSlug, Description: "当前启用的前端页面模板 slug", }) } } // ensureBuiltinTemplate 幂等种入内置模板 func ensureBuiltinTemplate(slug, name, desc string, sortOrder int) { var count int64 config.DB.Model(&models.Template{}). Where("slug = ? AND deleted_at = ?", slug, 0). Count(&count) if count > 0 { return } now := time.Now().Unix() t := models.Template{ Slug: slug, Name: name, Type: TemplateTypeBuiltin, Description: desc, IsSystem: true, IsActive: true, SortOrder: sortOrder, CreatedAt: now, UpdatedAt: now, } if err := config.DB.Create(&t).Error; err != nil { log.Printf("Failed to seed builtin template %s: %v", slug, err) } } // MigrateOSSConfigFields 为 oss_configs 补齐各云厂商专用列(幂等) func MigrateOSSConfigFields() { log.Printf("Migrating oss_configs provider fields...") if !tableExists("oss_configs") { log.Printf("Table oss_configs does not exist, skip MigrateOSSConfigFields") return } columns := []struct { name string ddl string }{ {"oss_access_key_id", "ALTER TABLE `oss_configs` ADD COLUMN `oss_access_key_id` varchar(255) NOT NULL DEFAULT '' COMMENT '阿里云 AccessKeyId(AES加密)' AFTER `domain`"}, {"oss_access_key_secret", "ALTER TABLE `oss_configs` ADD COLUMN `oss_access_key_secret` varchar(255) NOT NULL DEFAULT '' COMMENT '阿里云 AccessKeySecret(AES加密)' AFTER `oss_access_key_id`"}, {"oss_endpoint", "ALTER TABLE `oss_configs` ADD COLUMN `oss_endpoint` varchar(255) NOT NULL DEFAULT '' COMMENT '阿里云 Endpoint' AFTER `oss_access_key_secret`"}, {"oss_bucket", "ALTER TABLE `oss_configs` ADD COLUMN `oss_bucket` varchar(255) NOT NULL DEFAULT '' COMMENT '阿里云 Bucket' AFTER `oss_endpoint`"}, {"oss_domain", "ALTER TABLE `oss_configs` ADD COLUMN `oss_domain` varchar(255) NOT NULL DEFAULT '' COMMENT '阿里云自定义域名' AFTER `oss_bucket`"}, {"qcloud_secret_id", "ALTER TABLE `oss_configs` ADD COLUMN `qcloud_secret_id` varchar(255) NOT NULL DEFAULT '' COMMENT '腾讯云 SecretId(AES加密)' AFTER `oss_domain`"}, {"qcloud_secret_key", "ALTER TABLE `oss_configs` ADD COLUMN `qcloud_secret_key` varchar(255) NOT NULL DEFAULT '' COMMENT '腾讯云 SecretKey(AES加密)' AFTER `qcloud_secret_id`"}, {"qcloud_region", "ALTER TABLE `oss_configs` ADD COLUMN `qcloud_region` varchar(255) NOT NULL DEFAULT '' COMMENT '腾讯云 Region' AFTER `qcloud_secret_key`"}, {"qcloud_bucket", "ALTER TABLE `oss_configs` ADD COLUMN `qcloud_bucket` varchar(255) NOT NULL DEFAULT '' COMMENT '腾讯云 Bucket' AFTER `qcloud_region`"}, {"qcloud_domain", "ALTER TABLE `oss_configs` ADD COLUMN `qcloud_domain` varchar(255) NOT NULL DEFAULT '' COMMENT '腾讯云自定义域名' AFTER `qcloud_bucket`"}, {"qiniu_access_key", "ALTER TABLE `oss_configs` ADD COLUMN `qiniu_access_key` varchar(255) NOT NULL DEFAULT '' COMMENT '七牛 AccessKey(AES加密)' AFTER `qcloud_domain`"}, {"qiniu_secret_key", "ALTER TABLE `oss_configs` ADD COLUMN `qiniu_secret_key` varchar(255) NOT NULL DEFAULT '' COMMENT '七牛 SecretKey(AES加密)' AFTER `qiniu_access_key`"}, {"qiniu_bucket", "ALTER TABLE `oss_configs` ADD COLUMN `qiniu_bucket` varchar(255) NOT NULL DEFAULT '' COMMENT '七牛 Bucket' AFTER `qiniu_secret_key`"}, {"qiniu_region", "ALTER TABLE `oss_configs` ADD COLUMN `qiniu_region` varchar(255) NOT NULL DEFAULT '' COMMENT '七牛 Region' AFTER `qiniu_bucket`"}, {"qiniu_domain", "ALTER TABLE `oss_configs` ADD COLUMN `qiniu_domain` varchar(255) NOT NULL DEFAULT '' COMMENT '七牛自定义域名' AFTER `qiniu_region`"}, } for _, col := range columns { if !columnExists("oss_configs", col.name) { log.Printf("Adding oss_configs.%s...", col.name) execSQL(col.ddl) } } } // ensurePptTemplateColumnComments 为已存在但缺少字段备注的 ppt_templates 表补全 COMMENT func ensurePptTemplateColumnComments() { if !tableExists("ppt_templates") { return } alters := []string{ "ALTER TABLE `ppt_templates` MODIFY COLUMN `id` int UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '模板ID'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模板显示名称'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `slug` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'URL唯一标识(系统默认为default)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '模板描述'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `config` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'PPT样式配置JSON(配色/动画/切换/UI)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `is_system` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否系统内置模板(0否 1是,不可删除)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `is_default` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否演示默认模板(0否 1是)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `sort_order` int NOT NULL DEFAULT 0 COMMENT '排序(越小越靠前)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `is_active` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用(0否 1是)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `deleted_at` bigint NOT NULL DEFAULT 0 COMMENT '软删除时间戳'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `created_at` bigint NOT NULL DEFAULT 0 COMMENT '创建时间(Unix秒)'", "ALTER TABLE `ppt_templates` MODIFY COLUMN `updated_at` bigint NOT NULL DEFAULT 0 COMMENT '更新时间(Unix秒)'", "ALTER TABLE `ppt_templates` COMMENT='PPT模板表'", } for _, sql := range alters { execSQL(sql) } }