diff --git a/internal/auth/bootstrap.go b/internal/auth/bootstrap.go index 5b538fa6..c1c92870 100644 --- a/internal/auth/bootstrap.go +++ b/internal/auth/bootstrap.go @@ -2,13 +2,25 @@ package auth import ( "context" + "errors" "fmt" "os" + "strings" "chat-agent-system/internal/config" + "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgxpool" ) +func isUniqueViolation(err error) bool { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) && pgErr.Code == "23505" { + return true + } + msg := err.Error() + return strings.Contains(msg, "23505") || strings.Contains(msg, "duplicate key") +} + // BootstrapAdminFromConfig 确保配置的管理员账户在 PG 中存在且具备 role-admin。 func BootstrapAdminFromConfig(ctx context.Context, store AuthStore, cfg *config.Config) error { if store == nil || cfg == nil { @@ -102,6 +114,28 @@ func BootstrapDevSeedUsers(ctx context.Context, store AuthStore, cfg *config.Con EmployeeID: su.empID, }) if err != nil { + // Concurrent NewServer bootstrap and sequence drift after fixed-id + // goose seeds can race on users_pkey/username. If the demo user + // exists afterwards, treat the seed as successful. + if _, getErr := store.GetUserByUsername(ctx, su.username); getErr == nil { + continue + } + if isUniqueViolation(err) { + if _, retryErr := store.CreateUser(ctx, &UserAccount{ + Username: su.username, + Name: su.name, + PasswordHash: passHash, + Status: StatusActive, + RoleID: su.roleID, + Position: su.position, + Department: su.dept, + EmployeeID: su.empID, + }); retryErr == nil { + continue + } else if _, getErr := store.GetUserByUsername(ctx, su.username); getErr == nil { + continue + } + } return fmt.Errorf("seed user %s: %w", su.username, err) } }