198 lines
6.3 KiB
Go
198 lines
6.3 KiB
Go
package repositories
|
||
|
||
import (
|
||
"fmt"
|
||
"log"
|
||
|
||
"github.com/niangaodev/art-code/config"
|
||
)
|
||
|
||
// 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`")
|
||
}
|
||
}
|