记录一下如何使用 gorm 实现任意层级的文件夹模型,代码如下

type AdFolder struct {
	ID        uint64 `gorm:"primarykey"`           // 数据库ID
	CreatedAt int64  `gorm:"autoCreateTime:milli"` // 记录创建时间,单位 milli
	UpdatedAt int64  `gorm:"autoUpdateTime:milli"` // 记录更新时间,单位 milli

	AppID       uint64 `gorm:"index:idx_ad_folders_type"` // 应用ID
	Type        string `gorm:"index:idx_ad_folders_type"` // 分类类型
	Title       string // 标题
	Description string // 说明
	ParentID    uint64 `gorm:"index"` // 父文件夹ID
	RootID      uint64 `gorm:"index"` // 根文件夹ID

	CreatorID    uint64 // 创建用户ID
	LastEditorID uint64 // 最后编辑用户ID

	Parent      *AdFolder   `gorm:"foreignKey:ParentID"` // 父节点
	Children    []*AdFolder `gorm:"foreignKey:ParentID"` // 直系子节点
	Root        *AdFolder   `gorm:"foreignKey:RootID"`   // 根节点
	Descendants []*AdFolder `gorm:"foreignKey:RootID"`   // 根系子节点
}


// 获取根文件夹ID
func (a *AdFolder) GetFolderRootID() uint64 {
	var rootID uint64
	if a.RootID != 0 {
		rootID = a.RootID
	} else if a.ID != 0 {
		rootID = a.ID
	}
	return rootID
}

// 获取相同根节点的文件夹列表
func (a *AdFolder) GetSameRootAdFolders(tx *gorm.DB) ([]*AdFolder, error) {
	var adFolders []*AdFolder
	query := tx.Model(&AdFolder{}).
		Where("app_id = ?", a.AppID).
		Where("type = ?", a.Type)
	if a.RootID == 0 {
		query = query.Where("(id = ? OR root_id = ?)", a.ID, a.ID)
	} else {
		query = query.Where("(id = ? OR root_id = ?)", a.RootID, a.RootID)
	}
	if err := query.Order("id DESC").
		Find(&adFolders).Error; err != nil {
		return nil, err
	}
	return adFolders, nil
}

// 构造文件夹父节点数组
func (a *AdFolder) GetAdFolderParents(tx *gorm.DB) ([]*AdFolder, error) {
	adFolders, err := a.GetSameRootAdFolders(tx)
	if err != nil {
		return nil, err
	}
	adFoldersMap := make(map[uint64]*AdFolder, len(adFolders))
	parentAdFoldersMap := make(map[uint64]uint64, len(adFolders)) // children : parent
	for _, folder := range adFolders {
		adFoldersMap[folder.ID] = folder
		if folder.ParentID != 0 {
			parentAdFoldersMap[folder.ID] = folder.ParentID
		}
	}

	var parents []*AdFolder
	currentID := a.ID
	for {
		parentID, exists := parentAdFoldersMap[currentID]
		if !exists {
			break
		}

		parent, ok := adFoldersMap[parentID]
		if !ok {
			break
		}

		parents = append([]*AdFolder{parent}, parents...)
		currentID = parentID
	}
	return parents, nil
}

// 构造文件夹树形结构
func BuildAdFolderTree(adFolders []*AdFolder) []*AdFolder {
	adFoldersMap := make(map[uint64]*AdFolder, len(adFolders))
	for _, adFolder := range adFolders {
		adFoldersMap[adFolder.ID] = adFolder
	}

	var roots []*AdFolder
	for _, adFolder := range adFolders {
		if adFolder.ParentID == 0 {
			roots = append(roots, adFolder)
		} else {
			if parent, ok := adFoldersMap[adFolder.ParentID]; ok {
				parent.Children = append(parent.Children, adFolder)
				adFoldersMap[adFolder.ParentID] = parent
			}
		}
	}
	return roots
}

// 寻找所有后代节点
func (a *AdFolder) GetAdFolderDescendants(tx *gorm.DB) ([]*AdFolder, error) {
	adFolders, err := a.GetSameRootAdFolders(tx)
	if err != nil {
		return nil, err
	}
	adFoldersMap := make(map[uint64]*AdFolder)
	childrenMap := make(map[uint64][]uint64)

	for _, adFolder := range adFolders {
		adFoldersMap[adFolder.ID] = adFolder
		if adFolder.ParentID != 0 {
			childrenMap[adFolder.ParentID] = append(childrenMap[adFolder.ParentID], adFolder.ID)
		}
	}

	var children []*AdFolder
	queue := []uint64{a.ID}
	for len(queue) > 0 {
		currentID := queue[0]
		queue = queue[1:]
		for _, childID := range childrenMap[currentID] {
			if child, exists := adFoldersMap[childID]; exists {
				children = append(children, child)
				queue = append(queue, childID)
			}
		}
	}
	return children, nil
}