package logic import ( "context" "errors" "strings" "time" "spark-server/internal/module/user/entity" "spark-server/internal/pkg/errs" ) // UserDataCleaner removes a canceled user's rows in another module. type UserDataCleaner interface { PurgeUser(ctx context.Context, userID int64) error } // TokenRevoker drops every live JWT pair for a user. Auth JWT implements it. type TokenRevoker interface { LogoutAll(ctx context.Context, userID int64) error } // SessionDropper kicks every TCP connection for a user. type SessionDropper interface { KickUser(userID int64) } type cleanerFunc func(context.Context, int64) error func (f cleanerFunc) PurgeUser(ctx context.Context, userID int64) error { if f == nil { return nil } return f(ctx, userID) } // CleanerFunc adapts a function to UserDataCleaner. func CleanerFunc(fn func(context.Context, int64) error) UserDataCleaner { return cleanerFunc(fn) } // BindAccountCleanup wires token revoke, TCP kick, and cross-module purge for 注销. func (s *Service) BindAccountCleanup(tokens TokenRevoker, sessions SessionDropper, cleaners ...UserDataCleaner) { if s == nil { return } s.tokens = tokens s.sessions = sessions s.cleaners = append(s.cleaners[:0], cleaners...) } func (s *Service) DeleteAccount(ctx context.Context, userID int64) error { if !s.ready() || userID <= 0 { return errs.ErrUnauthorized } u, err := s.repo.GetByID(ctx, userID) if err != nil { return err } email, err := s.lookupEmail(ctx, userID) if err != nil { return err } s.revokeAccess(ctx, userID) if err := s.purgeRelated(ctx, userID); err != nil { return err } if err := s.repo.ArchiveAndDelete(ctx, entity.SnapshotDeleted(u, email, time.Now())); err != nil { return err } s.deleteAvatarObject(ctx, u.Avatar) if email != "" && s.codes != nil { _ = s.codes.Del(ctx, email) } return nil } func (s *Service) lookupEmail(ctx context.Context, userID int64) (string, error) { email, err := s.repo.GetEmail(ctx, userID) if err == nil { return email, nil } if errors.Is(err, errs.ErrNotFound) { return "", nil } return "", err } func (s *Service) revokeAccess(ctx context.Context, userID int64) { if s.tokens != nil { _ = s.tokens.LogoutAll(ctx, userID) } if s.sessions != nil { s.sessions.KickUser(userID) } } func (s *Service) purgeRelated(ctx context.Context, userID int64) error { for _, c := range s.cleaners { if c == nil { continue } if err := c.PurgeUser(ctx, userID); err != nil { return err } } return nil } type objectRemover interface { Delete(ctx context.Context, key string) error } func (s *Service) deleteAvatarObject(ctx context.Context, avatar string) { key := strings.TrimSpace(avatar) if !strings.HasPrefix(key, "perm/avatar/") { return } rm, ok := s.objects.(objectRemover) if !ok || rm == nil { return } _ = rm.Delete(ctx, key) }