spark-server/internal/module/user/logic/delete.go
2026-09-20 17:49:09 +08:00

125 lines
2.8 KiB
Go

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)
}