diff --git a/server/internal/db/migrate.go b/server/internal/db/migrate.go index c04319b..105bd6f 100644 --- a/server/internal/db/migrate.go +++ b/server/internal/db/migrate.go @@ -120,13 +120,21 @@ func switchoverAt(ctx context.Context) (time.Time, error) { return time.Time{}, err } var raw sql.NullString - if err := DB.QueryRowContext(ctx, `SELECT MIN(read_at) FROM mail_reads`).Scan(&raw); err != nil { + // CAST 成文本再解析:两种方言的驱动对 timestamptz 返回的类型不一样 + // (PG 给 time.Time,SQLite 给字符串),文本形态才是共通的。 + if err := DB.QueryRowContext(ctx, `SELECT CAST(MIN(read_at) AS TEXT) FROM mail_reads`).Scan(&raw); err != nil { return time.Time{}, err } if !raw.Valid { return time.Now().UTC(), nil // 表还是空的:本机没有"老邮件"要照顾 } - for _, layout := range []string{"2006-01-02 15:04:05.000", "2006-01-02 15:04:05", time.RFC3339} { + // 认不出来的形态退回"现在":那等于把当下已有的已读邮件全算作迁移前 —— + // 首次升级时正是对的;而且这条路有一次性标记守着,不会反复跑。 + for _, layout := range []string{ + "2006-01-02 15:04:05.000", "2006-01-02 15:04:05", + "2006-01-02 15:04:05.999999-07", "2006-01-02 15:04:05-07", + time.RFC3339, + } { if t, perr := time.Parse(layout, raw.String); perr == nil { return t.UTC(), nil }