MenuRepository.kt

package io.github.lishangbu.avalon.auth.repository

import io.github.lishangbu.avalon.auth.entity.*
import io.github.lishangbu.avalon.auth.entity.dto.MenuTreeView
import io.github.lishangbu.avalon.auth.entity.dto.MenuView
import org.babyfish.jimmer.Specification
import org.babyfish.jimmer.spring.repository.KRepository
import org.babyfish.jimmer.spring.repository.orderBy
import org.babyfish.jimmer.sql.kt.ast.expression.eq
import org.babyfish.jimmer.sql.kt.ast.expression.isNull
import org.springframework.data.domain.Sort

/**
 * 菜单仓储接口
 *
 * 定义菜单数据的查询与持久化操作
 *
 * @author lishangbu
 * @since 2025/08/20
 */
interface MenuRepository : KRepository<Menu, Long> {
    /** 按条件查询菜单列表 */
    fun findAll(specification: Specification<Menu>?): List<MenuView> =
        sql
            .createQuery(Menu::class) {
                specification?.let(::where)
                orderBy(DEFAULT_SORT)
                select(table.fetch(MenuView::class))
            }.execute()

    /** 按条件查询菜单视图列表 */
    fun listViews(specification: Specification<Menu>?): List<MenuView> =
        sql
            .createQuery(Menu::class) {
                specification?.let(::where)
                orderBy(DEFAULT_SORT)
                select(table.fetch(MenuView::class))
            }.execute()

    /** 查询根节点菜单树 */
    fun listTreeViews(): List<MenuTreeView> =
        sql
            .createQuery(Menu::class) {
                where(table.parentId.isNull())
                orderBy(DEFAULT_SORT)
                select(table.fetch(MenuTreeView::class))
            }.execute()

    /** 判断是否存在子菜单 */
    fun hasChildren(parentId: Long): Boolean =
        sql
            .createQuery(Menu::class) {
                where(table.parentId eq parentId)
                select(table.id)
            }.execute()
            .isNotEmpty()

    /** 按 ID 查询菜单视图 */
    fun loadViewById(id: Long): MenuView? =
        sql
            .createQuery(Menu::class) {
                where(table.id eq id)
                select(table.fetch(MenuView::class))
            }.execute()
            .firstOrNull()

    /** 按角色编码列表查询授权菜单视图 */
    fun listViewsByRoleCodes(roleCodes: List<String>): List<MenuView> {
        if (roleCodes.isEmpty()) {
            return emptyList()
        }
        val menus =
            roleCodes.flatMap { roleCode ->
                sql
                    .createQuery(Role::class) {
                        where(table.code eq roleCode)
                        select(table.fetch(AuthorizationFetchers.ROLE_WITH_MENUS))
                    }.execute()
                    .flatMap { role -> role.menus }
                    .map(::MenuView)
            }
        return menus
            .distinctBy { it.id }
            .sortedWith(compareBy<MenuView> { it.sortingOrder ?: Int.MAX_VALUE }.thenBy { it.id })
    }

    companion object {
        private val DEFAULT_SORT: Sort =
            Sort.by(Sort.Order.asc("sortingOrder"), Sort.Order.asc("id"))
    }
}