RoleServiceImpl.kt

package io.github.lishangbu.avalon.auth.service.impl

import io.github.lishangbu.avalon.auth.entity.Role
import io.github.lishangbu.avalon.auth.entity.addBy
import io.github.lishangbu.avalon.auth.entity.dto.RoleSpecification
import io.github.lishangbu.avalon.auth.entity.dto.RoleView
import io.github.lishangbu.avalon.auth.entity.dto.SaveRoleInput
import io.github.lishangbu.avalon.auth.entity.dto.UpdateRoleInput
import io.github.lishangbu.avalon.auth.repository.AuthorizationFetchers
import io.github.lishangbu.avalon.auth.repository.MenuRepository
import io.github.lishangbu.avalon.auth.repository.PermissionRepository
import io.github.lishangbu.avalon.auth.repository.RoleRepository
import io.github.lishangbu.avalon.auth.service.RoleService
import io.github.lishangbu.avalon.jimmer.support.readOrNull
import org.babyfish.jimmer.Page
import org.babyfish.jimmer.sql.ast.mutation.SaveMode
import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

/** 角色服务实现。 */
@Service
class RoleServiceImpl(
    private val roleRepository: RoleRepository,
    private val menuRepository: MenuRepository,
    private val permissionRepository: PermissionRepository,
) : RoleService {
    override fun getPageByCondition(
        specification: RoleSpecification,
        pageable: Pageable,
    ): Page<RoleView> = roleRepository.pageViews(specification, pageable)

    override fun listByCondition(specification: RoleSpecification): List<RoleView> = roleRepository.listViews(specification)

    override fun getById(id: Long): RoleView? = roleRepository.loadViewById(id)

    @Transactional(rollbackFor = [Exception::class])
    override fun save(command: SaveRoleInput): RoleView {
        val prepared = bindAssociations(command.toEntity(), false)
        return roleRepository.save(prepared, SaveMode.INSERT_ONLY).let(::reloadView)
    }

    @Transactional(rollbackFor = [Exception::class])
    override fun update(command: UpdateRoleInput): RoleView {
        val prepared = bindAssociations(command.toEntity(), true)
        return roleRepository.save(prepared).let(::reloadView)
    }

    @Transactional(rollbackFor = [Exception::class])
    override fun removeById(id: Long) {
        roleRepository.deleteById(id)
    }

    private fun bindAssociations(
        role: Role,
        preserveWhenNull: Boolean,
    ): Role {
        val existing =
            if (preserveWhenNull) {
                role.readOrNull { id }?.let { roleId ->
                    roleRepository.findNullable(roleId, AuthorizationFetchers.ROLE_WITH_BINDINGS)
                }
            } else {
                null
            }

        val currentMenus = role.readOrNull { menus }
        val menuIds = currentMenus?.mapNotNull { it.readOrNull { id } }?.toCollection(LinkedHashSet())
        val shouldLoadMenus = currentMenus != null
        val boundMenus =
            when {
                currentMenus != null && !menuIds.isNullOrEmpty() -> menuRepository.findAllById(menuIds)
                currentMenus != null -> emptyList()
                preserveWhenNull -> existing?.readOrNull { menus } ?: emptyList()
                else -> emptyList()
            }

        val currentPermissions = role.readOrNull { permissions }
        val permissionIds =
            currentPermissions?.mapNotNull { it.readOrNull { id } }?.toCollection(LinkedHashSet())
        val shouldLoadPermissions = currentPermissions != null
        val boundPermissions =
            when {
                currentPermissions != null && !permissionIds.isNullOrEmpty() -> permissionRepository.findAllById(permissionIds)
                currentPermissions != null -> emptyList()
                preserveWhenNull -> existing?.readOrNull { permissions } ?: emptyList()
                else -> emptyList()
            }

        return Role {
            role.readOrNull { id }?.let { id = it }
            code = role.readOrNull { code } ?: existing?.readOrNull { code }
            name = role.readOrNull { name } ?: existing?.readOrNull { name }
            enabled = role.readOrNull { enabled } ?: existing?.readOrNull { enabled }

            if (shouldLoadMenus) {
                menus()
            }
            boundMenus.forEach { boundMenu -> menus().addBy(boundMenu) }

            if (shouldLoadPermissions) {
                permissions()
            }
            boundPermissions.forEach { boundPermission -> permissions().addBy(boundPermission) }
        }
    }

    private fun reloadView(role: Role): RoleView =
        requireNotNull(roleRepository.loadViewById(role.id)) {
            "未找到 ID=${role.id} 对应的角色"
        }
}