hi-server/internal/logic/public/user/familyBindingHelper.go
shanshanzhong a3cc23bbd4
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 8m5s
feat: 绑定新邮箱时创建独立邮箱用户并转移订阅,而非挂在设备用户上
- bindEmailWithVerificationLogic: 新邮箱路径改为创建独立 email user + joinFamily
- familyBindingHelper: clearMemberSubscribes → transferMemberSubscribesToOwner,订阅转移给 owner 而非删除
- accountMergeHelper: 同步更新调用点

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-03-12 00:52:50 -07:00

317 lines
10 KiB
Go

package user
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type familyJoinResult struct {
FamilyId int64
OwnerUserId int64
}
type familyBindingHelper struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func newFamilyBindingHelper(ctx context.Context, svcCtx *svc.ServiceContext) *familyBindingHelper {
return &familyBindingHelper{
ctx: ctx,
svcCtx: svcCtx,
}
}
func (h *familyBindingHelper) getUserEmailMethod(userId int64) (*user.AuthMethods, error) {
method, err := h.svcCtx.UserModel.FindUserAuthMethodByUserId(h.ctx, "email", userId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user email auth method failed")
}
return method, nil
}
func validateMemberJoinConflict(ownerFamilyId int64, memberRecord *user.UserFamilyMember) error {
if memberRecord == nil {
return nil
}
if ownerFamilyId != 0 && memberRecord.FamilyId == ownerFamilyId {
if memberRecord.Status == user.FamilyMemberActive {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already in this family")
}
return nil
}
if memberRecord.Status == user.FamilyMemberActive {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyCrossBindForbidden), "user already belongs to another family")
}
return nil
}
func (h *familyBindingHelper) validateJoinFamily(ownerUserId, memberUserId int64) error {
if ownerUserId == memberUserId {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already bound to this family")
}
var ownerFamily user.UserFamily
err := h.svcCtx.DB.WithContext(h.ctx).
Unscoped().
Model(&user.UserFamily{}).
Where("owner_user_id = ?", ownerUserId).
First(&ownerFamily).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family failed")
}
var memberRecord user.UserFamilyMember
memberExists := false
err = h.svcCtx.DB.WithContext(h.ctx).
Unscoped().
Model(&user.UserFamilyMember{}).
Where("user_id = ?", memberUserId).
First(&memberRecord).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query member family relation failed")
}
if err == nil {
memberExists = true
}
if memberExists {
if err = validateMemberJoinConflict(ownerFamily.Id, &memberRecord); err != nil {
return err
}
}
if ownerFamily.Id == 0 {
return nil
}
var activeCount int64
if err = h.svcCtx.DB.WithContext(h.ctx).
Model(&user.UserFamilyMember{}).
Where("family_id = ? AND status = ?", ownerFamily.Id, user.FamilyMemberActive).
Count(&activeCount).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count family members failed")
}
if activeCount >= ownerFamily.MaxMembers {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyMemberLimitExceeded), "family member limit exceeded")
}
return nil
}
func (h *familyBindingHelper) joinFamily(ownerUserId, memberUserId int64, source string) (*familyJoinResult, error) {
if ownerUserId == memberUserId {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already bound to this family")
}
result := &familyJoinResult{
OwnerUserId: ownerUserId,
}
removedSubscribes := make([]user.Subscribe, 0)
err := h.svcCtx.DB.WithContext(h.ctx).Transaction(func(tx *gorm.DB) error {
ownerFamily, err := h.getOrCreateOwnerFamily(tx, ownerUserId)
if err != nil {
return err
}
result.FamilyId = ownerFamily.Id
if err = h.ensureOwnerMember(tx, ownerFamily.Id, ownerUserId); err != nil {
return err
}
var memberRecord user.UserFamilyMember
err = tx.Unscoped().Model(&user.UserFamilyMember{}).Where("user_id = ?", memberUserId).First(&memberRecord).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query member family relation failed")
}
memberExists := err == nil
if memberExists {
if err = validateMemberJoinConflict(ownerFamily.Id, &memberRecord); err != nil {
return err
}
}
var activeCount int64
if err = tx.Model(&user.UserFamilyMember{}).
Where("family_id = ? AND status = ?", ownerFamily.Id, user.FamilyMemberActive).
Count(&activeCount).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count family members failed")
}
if activeCount >= ownerFamily.MaxMembers {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyMemberLimitExceeded), "family member limit exceeded")
}
now := time.Now()
if !memberExists {
memberRecord = user.UserFamilyMember{
FamilyId: ownerFamily.Id,
UserId: memberUserId,
Role: user.FamilyRoleMember,
Status: user.FamilyMemberActive,
JoinSource: source,
JoinedAt: now,
}
if err = tx.Create(&memberRecord).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create family member failed")
}
} else {
if memberRecord.FamilyId != ownerFamily.Id {
memberRecord.FamilyId = ownerFamily.Id
}
memberRecord.Status = user.FamilyMemberActive
memberRecord.Role = user.FamilyRoleMember
memberRecord.JoinSource = source
memberRecord.JoinedAt = now
memberRecord.LeftAt = nil
memberRecord.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&memberRecord).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update family member failed")
}
}
removedSubscribes, err = transferMemberSubscribesToOwner(tx, memberUserId, ownerUserId)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
if err = h.clearRemovedMemberSubscribeCache(removedSubscribes); err != nil {
return nil, err
}
return result, nil
}
func transferMemberSubscribesToOwner(tx *gorm.DB, memberUserId, ownerUserId int64) ([]user.Subscribe, error) {
var subscribes []user.Subscribe
if err := tx.Model(&user.Subscribe{}).
Where("user_id = ?", memberUserId).
Find(&subscribes).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query member subscribe list failed")
}
if len(subscribes) == 0 {
return nil, nil
}
if err := tx.Model(&user.Subscribe{}).
Where("user_id = ?", memberUserId).
Update("user_id", ownerUserId).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "transfer member subscribes to owner failed")
}
return subscribes, nil
}
func (h *familyBindingHelper) clearRemovedMemberSubscribeCache(removedSubscribes []user.Subscribe) error {
if len(removedSubscribes) == 0 {
return nil
}
subscribeModels, subscribeIDSet := buildRemovedSubscribeCacheMeta(removedSubscribes)
if err := h.svcCtx.UserModel.ClearSubscribeCache(h.ctx, subscribeModels...); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "clear member subscribe cache failed")
}
for subscribeID := range subscribeIDSet {
if err := h.svcCtx.SubscribeModel.ClearCache(h.ctx, subscribeID); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "clear subscribe cache failed")
}
}
if err := h.svcCtx.NodeModel.ClearServerAllCache(h.ctx); err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "clear node cache failed")
}
return nil
}
func buildRemovedSubscribeCacheMeta(removedSubscribes []user.Subscribe) ([]*user.Subscribe, map[int64]struct{}) {
subscribeModels := make([]*user.Subscribe, 0, len(removedSubscribes))
subscribeIDSet := make(map[int64]struct{}, len(removedSubscribes))
for i := range removedSubscribes {
subscribeModels = append(subscribeModels, &removedSubscribes[i])
if removedSubscribes[i].SubscribeId > 0 {
subscribeIDSet[removedSubscribes[i].SubscribeId] = struct{}{}
}
}
return subscribeModels, subscribeIDSet
}
func (h *familyBindingHelper) getOrCreateOwnerFamily(tx *gorm.DB, ownerUserId int64) (*user.UserFamily, error) {
var ownerFamily user.UserFamily
err := tx.Unscoped().Clauses(clause.Locking{Strength: "UPDATE"}).
Model(&user.UserFamily{}).
Where("owner_user_id = ?", ownerUserId).
First(&ownerFamily).Error
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family failed")
}
ownerFamily = user.UserFamily{
OwnerUserId: ownerUserId,
MaxMembers: user.DefaultFamilyMaxSize,
Status: user.FamilyStatusActive,
}
if err = tx.Create(&ownerFamily).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create owner family failed")
}
} else if ownerFamily.Status != user.FamilyStatusActive || ownerFamily.DeletedAt.Valid {
ownerFamily.Status = user.FamilyStatusActive
ownerFamily.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&ownerFamily).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "activate owner family failed")
}
}
return &ownerFamily, nil
}
func (h *familyBindingHelper) ensureOwnerMember(tx *gorm.DB, familyId, ownerUserId int64) error {
var ownerMember user.UserFamilyMember
err := tx.Unscoped().Model(&user.UserFamilyMember{}).Where("user_id = ?", ownerUserId).First(&ownerMember).Error
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family member failed")
}
ownerMember = user.UserFamilyMember{
FamilyId: familyId,
UserId: ownerUserId,
Role: user.FamilyRoleOwner,
Status: user.FamilyMemberActive,
JoinSource: "owner_init",
JoinedAt: time.Now(),
}
if err = tx.Create(&ownerMember).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create owner family member failed")
}
return nil
}
if ownerMember.FamilyId != familyId {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyCrossBindForbidden), "owner already belongs to another family")
}
if ownerMember.Status != user.FamilyMemberActive || ownerMember.Role != user.FamilyRoleOwner || ownerMember.DeletedAt.Valid {
ownerMember.Status = user.FamilyMemberActive
ownerMember.Role = user.FamilyRoleOwner
ownerMember.LeftAt = nil
ownerMember.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&ownerMember).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update owner family member failed")
}
}
return nil
}