UserServiceImpl.kt
package io.github.lishangbu.avalon.auth.service.impl
import io.github.lishangbu.avalon.auth.entity.User
import io.github.lishangbu.avalon.auth.entity.addBy
import io.github.lishangbu.avalon.auth.entity.dto.CurrentUserView
import io.github.lishangbu.avalon.auth.entity.dto.SaveUserInput
import io.github.lishangbu.avalon.auth.entity.dto.UpdateUserInput
import io.github.lishangbu.avalon.auth.entity.dto.UserSpecification
import io.github.lishangbu.avalon.auth.entity.dto.UserView
import io.github.lishangbu.avalon.auth.repository.AuthorizationFetchers
import io.github.lishangbu.avalon.auth.repository.RoleRepository
import io.github.lishangbu.avalon.auth.repository.UserRepository
import io.github.lishangbu.avalon.auth.service.UserService
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
/**
* 用户服务实现
*
* @author lishangbu
* @since 2025/8/30
*/
@Service
class UserServiceImpl(
/** 用户仓储 */
private val userRepository: UserRepository,
/** 角色仓储 */
private val roleRepository: RoleRepository,
) : UserService {
/**
* 根据用户名/手机号/邮箱查询用户详情,包含基本信息、角色信息及个人资料
*
* @param username 登录账号
* @return 查询到的用户详情,未找到时返回 null
*/
override fun getUserByUsername(username: String): CurrentUserView? = userRepository.loadByAccountWithRoles(username)?.let(::CurrentUserView)
/** 获取当前用户的权限编码列表 */
override fun getCurrentPermissionCodes(username: String): List<String> =
userRepository
.loadByAccountWithRoles(username)
?.roles
?.flatMap { role -> role.permissions }
?.asSequence()
?.filter { permission -> permission.enabled != false }
?.mapNotNull { permission -> permission.code?.trim() }
?.filter { code -> code.isNotEmpty() }
?.distinct()
?.sorted()
?.toList()
?: emptyList()
/** 按条件分页查询用户 */
override fun getPageByCondition(
specification: UserSpecification,
pageable: Pageable,
): Page<UserView> = userRepository.pageViews(specification, pageable)
/** 按条件查询用户列表 */
override fun listByCondition(specification: UserSpecification): List<UserView> = userRepository.listViews(specification)
/** 按 ID 查询用户 */
override fun getById(id: Long): UserView? = userRepository.loadViewById(id)
/** 保存用户 */
@Transactional(rollbackFor = [Exception::class])
override fun save(command: SaveUserInput): UserView {
val prepared = bindRoles(command.toEntity(), false)
return userRepository.save(prepared, SaveMode.INSERT_ONLY).let(::reloadView)
}
/** 更新用户 */
@Transactional(rollbackFor = [Exception::class])
override fun update(command: UpdateUserInput): UserView {
val prepared = bindRoles(command.toEntity(), true)
return userRepository.save(prepared).let(::reloadView)
}
/** 按 ID 删除用户 */
@Transactional(rollbackFor = [Exception::class])
override fun removeById(id: Long) {
userRepository.deleteById(id)
}
/** 绑定并补全角色信息 */
private fun bindRoles(
user: User,
preserveWhenNull: Boolean,
): User {
val existing =
if (preserveWhenNull) {
user.readOrNull { id }?.let { userId -> userRepository.findNullable(userId, AuthorizationFetchers.USER_WITH_ROLES) }
} else {
null
}
val currentRoles = user.readOrNull { roles }
val roleIds = currentRoles?.mapNotNull { it.readOrNull { id } }?.toCollection(LinkedHashSet())
val shouldLoadRoles = currentRoles != null
val boundRoles =
when {
currentRoles != null && !roleIds.isNullOrEmpty() -> roleRepository.findAllById(roleIds)
currentRoles != null -> emptyList()
preserveWhenNull -> existing?.readOrNull { roles } ?: emptyList()
else -> emptyList()
}
val hashedPassword =
user.readOrNull { hashedPassword } ?: existing?.readOrNull { hashedPassword }
return User {
user.readOrNull { id }?.let { id = it }
username = user.readOrNull { username } ?: existing?.readOrNull { username }
phone = user.readOrNull { phone } ?: existing?.readOrNull { phone }
email = user.readOrNull { email } ?: existing?.readOrNull { email }
avatar = user.readOrNull { avatar } ?: existing?.readOrNull { avatar }
enabled = user.readOrNull { enabled } ?: existing?.readOrNull { enabled } ?: true
this.hashedPassword = hashedPassword
if (shouldLoadRoles) {
roles()
}
boundRoles.forEach { boundRole -> roles().addBy(boundRole) }
}
}
private fun reloadView(user: User): UserView = requireNotNull(userRepository.loadViewById(user.id)) { "未找到 ID=${user.id} 对应的用户" }
}