From 034901a46761c702ed6988468df6a69652d1fabc Mon Sep 17 00:00:00 2001 From: dsh Date: Wed, 19 Aug 2026 23:36:21 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(db):=20AutoMigrate=20=E5=89=8D=E6=B8=85?= =?UTF-8?q?=E7=90=86=E6=97=A7=E6=A8=A1=E5=9E=8B=E9=81=97=E7=95=99=E7=9A=84?= =?UTF-8?q?=E5=90=8C=E5=90=8D=E9=9D=9E=E5=94=AF=E4=B8=80=E7=B4=A2=E5=BC=95?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=20MySQL=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧模型 BanEntry.IPAddress 为普通 index,由 AutoMigrate 建成非唯一索引 idx_ban_entries_ip_address;升级到 uniqueIndex 后,gorm mysql 驱动 MigrateColumnUnique 对非唯一列无条件 CREATE UNIQUE INDEX(不检查 HasIndex),与同名旧索引冲突报 Error 1061 Duplicate key name,服务 启动即失败(AutoMigrate 末尾的 HasIndex 保护执行不到)。 dropLegacyBanIndex 在 dedupeBanEntries 之后、AutoMigrate 之前,检测 information_schema.statistics 中同名 non_unique=1 的索引并删除,由 AutoMigrate 重建为唯一索引。仅 MySQL 生效,表不存在/失败仅告警。 --- internal/db/db.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/internal/db/db.go b/internal/db/db.go index 6a06cf6..c9d0af3 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -60,6 +60,8 @@ func InitDB(cfg config.DatabaseConfig, storageCfg config.StorageConfig) (*gorm.D // ip_address 将升级为唯一索引,重复行会使索引创建失败。 // 首次安装表不存在时忽略错误(AutoMigrate 会建新表)。 dedupeBanEntries(db) + // 再清理旧模型遗留的同名非唯一索引(见 dropLegacyBanIndex)。 + dropLegacyBanIndex(db) // Auto-migrate all models if err := db.AutoMigrate(&User{}, &Domain{}, &Message{}, &Attachment{}, &BanEntry{}, &OutboundMessage{}, &ProtocolLog{}, &MailboxState{}, &Mailbox{}); err != nil { @@ -87,3 +89,38 @@ func dedupeBanEntries(db *gorm.DB) { log.Printf("清理 ban_entries 重复行失败(唯一索引可能无法创建): %v", err) } } + +// dropLegacyBanIndex 删除 ban_entries 上历史遗留的同名非唯一索引 +// (旧模型 IPAddress 为普通 `index` 时由 AutoMigrate 创建)。 +// IPAddress 升级为 uniqueIndex 后,MySQL 的 MigrateColumnUnique 会对 +// 非唯一列无条件执行 CREATE UNIQUE INDEX(该路径不检查 HasIndex), +// 与同名旧索引冲突时报 Error 1061 Duplicate key name,导致启动即失败 +// (AutoMigrate 末尾按名查 HasIndex 的保护根本执行不到)。 +// 仅 MySQL 存在此问题;SQLite 迁移路径不受影响。 +// 表不存在(首次安装)时静默跳过;失败仅告警,不阻断启动 +// (索引创建失败会在 AutoMigrate 中显式报错)。 +func dropLegacyBanIndex(db *gorm.DB) { + if db.Dialector.Name() != "mysql" { + return + } + var cnt int64 + err := db.Raw( + "SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'ban_entries' AND index_name = 'idx_ban_entries_ip_address' AND non_unique = 1", + ).Scan(&cnt).Error + if err != nil { + // 表不存在(首次安装)为预期情况 + if strings.Contains(err.Error(), "doesn't exist") { + return + } + log.Printf("检查 ban_entries 遗留索引失败(唯一索引可能无法创建): %v", err) + return + } + if cnt == 0 { + return + } + if err := db.Exec("ALTER TABLE `ban_entries` DROP INDEX `idx_ban_entries_ip_address`").Error; err != nil { + log.Printf("删除 ban_entries 遗留非唯一索引失败(唯一索引可能无法创建): %v", err) + return + } + log.Printf("已删除 ban_entries 遗留非唯一索引 idx_ban_entries_ip_address(AutoMigrate 将重建为唯一索引)") +} From 0576e6e348852800da33418d6616e17855e46689 Mon Sep 17 00:00:00 2001 From: dsh Date: Wed, 19 Aug 2026 23:39:49 -0400 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=E5=BF=BD=E7=95=A5=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E6=9E=84=E5=BB=BA=E4=BA=A7=E7=89=A9=20bin/=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 40df21b..f589aba 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ win/srv/ # 临时测试文件 login_test.html migrate + +# 本地构建产物 +bin/