Add user management api

This commit is contained in:
2023-11-28 18:16:45 +08:00
parent 91e7a6946f
commit f0f49b6d4c
12 changed files with 627 additions and 14 deletions

View File

@@ -2,12 +2,17 @@ package top.fatweb.api.controller.permission
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import top.fatweb.api.converter.permission.UserConverter
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.*
import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult
import top.fatweb.api.param.authentication.UserAddParam
import top.fatweb.api.param.authentication.UserDeleteParam
import top.fatweb.api.param.authentication.UserGetParam
import top.fatweb.api.param.authentication.UserUpdateParam
import top.fatweb.api.service.permission.IUserService
import top.fatweb.api.vo.PageVo
import top.fatweb.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.api.vo.permission.UserWithPowerInfoVo
import top.fatweb.api.vo.permission.UserWithRoleInfoVo
@@ -27,15 +32,60 @@ class UserController(
@GetMapping("info")
fun getInfo(): ResponseResult<UserWithPowerInfoVo> {
userService.getInfo()?.let {
return ResponseResult.databaseSuccess(data = UserConverter.userToUserWithPowerInfoVo(it))
return ResponseResult.databaseSuccess(data = it)
} ?: let { return ResponseResult.databaseFail() }
}
@Operation(summary = "获取用户列表")
@Operation(summary = "获取用户")
@GetMapping
fun get(): ResponseResult<List<UserWithRoleInfoVo>> {
fun get(@Valid userGetParam: UserGetParam?): ResponseResult<PageVo<UserWithRoleInfoVo>> {
return ResponseResult.databaseSuccess(
data = userService.getList().map { UserConverter.userToUserWithRoleInfoVo(it) })
data = userService.getPage(userGetParam)
)
}
@Operation(summary = "获取单个用户")
@GetMapping("/{id}")
fun getOne(@PathVariable id: Long): ResponseResult<UserWithRoleInfoVo> {
return userService.getOne(id)?.let {
ResponseResult.databaseSuccess(data = it)
} ?: let {
ResponseResult.databaseFail(ResponseCode.DATABASE_NO_RECORD_FOUND)
}
}
@Operation(summary = "添加用户")
@PostMapping
fun add(@Valid @RequestBody userAddParam: UserAddParam): ResponseResult<UserWithPasswordRoleInfoVo> {
return userService.add(userAddParam)?.let {
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS, data = it
)
} ?: let { ResponseResult.databaseFail(ResponseCode.DATABASE_INSERT_FAILED) }
}
@Operation(summary = "修改用户")
@PutMapping
fun update(@Valid @RequestBody userUpdateParam: UserUpdateParam): ResponseResult<UserWithRoleInfoVo> {
return userService.update(userUpdateParam)?.let {
ResponseResult.databaseSuccess(
ResponseCode.DATABASE_INSERT_SUCCESS, data = it
)
} ?: let { ResponseResult.databaseFail(ResponseCode.DATABASE_INSERT_FAILED) }
}
@Operation(summary = "删除用户")
@DeleteMapping("/{id}")
fun delete(@PathVariable id: Long): ResponseResult<Nothing> {
userService.deleteOne(id)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
@Operation(summary = "批量删除用户")
@DeleteMapping
fun deleteList(@Valid @RequestBody userDeleteParam: UserDeleteParam): ResponseResult<Nothing> {
userService.delete(userDeleteParam)
return ResponseResult.databaseSuccess(ResponseCode.DATABASE_DELETE_SUCCESS)
}
}

View File

@@ -1,8 +1,18 @@
package top.fatweb.api.converter.permission
import com.baomidou.mybatisplus.core.metadata.IPage
import top.fatweb.api.entity.permission.Group
import top.fatweb.api.entity.permission.Role
import top.fatweb.api.entity.permission.User
import top.fatweb.api.entity.permission.UserInfo
import top.fatweb.api.param.authentication.LoginParam
import top.fatweb.api.vo.permission.*
import top.fatweb.api.param.authentication.UserAddParam
import top.fatweb.api.param.authentication.UserUpdateParam
import top.fatweb.api.vo.PageVo
import top.fatweb.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.api.vo.permission.UserWithPowerInfoVo
import top.fatweb.api.vo.permission.UserWithRoleInfoVo
import top.fatweb.avatargenerator.GitHubAvatar
/**
* User converter
@@ -11,6 +21,7 @@ import top.fatweb.api.vo.permission.*
* @since 1.0.0
*/
object UserConverter {
fun loginParamToUser(loginParam: LoginParam) = User().apply {
username = loginParam.username
password = loginParam.password
@@ -69,4 +80,72 @@ object UserConverter {
GroupConverter.groupToGroupVo(it)
}
)
fun userToUserWithPasswordRoleInfoVo(user: User) = UserWithPasswordRoleInfoVo(
id = user.id,
username = user.username,
password = user.password,
locking = user.locking?.let { it == 1 },
expiration = user.expiration,
credentialsExpiration = user.credentialsExpiration,
enable = user.enable?.let { it == 1 },
currentLoginTime = user.currentLoginTime,
currentLoginIp = user.currentLoginIp,
lastLoginTime = user.lastLoginTime,
lastLoginIp = user.lastLoginIp,
createTime = user.createTime,
updateTime = user.updateTime,
userInfo = user.userInfo?.let {
UserInfoConverter.userInfoToUserInfoVo(it)
},
roles = user.roles?.map {
RoleConverter.roleToRoleVo(it)
},
groups = user.groups?.map {
GroupConverter.groupToGroupVo(it)
}
)
fun userAddParamToUser(userAddParam: UserAddParam) = User().apply {
username = userAddParam.username
password = userAddParam.password
locking = if (userAddParam.locking) 1 else 0
expiration = userAddParam.expiration
credentialsExpiration = userAddParam.credentialsExpiration
enable = if (userAddParam.enable) 1 else 0
userInfo = UserInfo().apply {
nickname = userAddParam.nickname ?: userAddParam.username
avatar = userAddParam.avatar ?: GitHubAvatar.newAvatarBuilder().build()
.createAsBase64((Long.MIN_VALUE..Long.MAX_VALUE).random())
email = userAddParam.email
}
roles = userAddParam.roleIds?.map { Role().apply { id = it } }
groups = userAddParam.groupIds?.map { Group().apply { id = it } }
}
fun userUpdateParamToUser(userUpdateParam: UserUpdateParam) = User().apply {
id = userUpdateParam.id
username = userUpdateParam.username
locking = if (userUpdateParam.locking && userUpdateParam.id != 0L) 1 else 0
expiration = if (userUpdateParam.id != 0L) userUpdateParam.expiration else null
credentialsExpiration = userUpdateParam.credentialsExpiration
enable = if (userUpdateParam.enable || userUpdateParam.id == 0L) 1 else 0
userInfo = UserInfo().apply {
nickname = userUpdateParam.nickname
avatar = userUpdateParam.avatar
email = userUpdateParam.email
}
roles = userUpdateParam.roleIds?.map { Role().apply { id = it } }
groups = userUpdateParam.groupIds?.map { Group().apply { id = it } }
}
fun userPageToUserWithRoleInfoPageVo(userPage: IPage<User>) = PageVo(
total = userPage.total,
pages = userPage.pages,
size = userPage.size,
current = userPage.current,
records = userPage.records.map {
userToUserWithRoleInfoVo(it)
}
)
}

View File

@@ -42,6 +42,7 @@ enum class ResponseCode(val code: Int) {
DATABASE_DELETE_FILED(BusinessCode.DATABASE, 35),
DATABASE_EXECUTE_ERROR(BusinessCode.DATABASE, 50),
DATABASE_DUPLICATE_KEY(BusinessCode.DATABASE, 51),
DATABASE_NO_RECORD_FOUND(BusinessCode.DATABASE, 52),
API_AVATAR_SUCCESS(BusinessCode.API_AVATAR, 0),
API_AVATAR_ERROR(BusinessCode.API_AVATAR, 50);

View File

@@ -1,6 +1,7 @@
package top.fatweb.api.mapper.permission
import com.baomidou.mybatisplus.core.mapper.BaseMapper
import com.baomidou.mybatisplus.core.metadata.IPage
import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Param
import top.fatweb.api.entity.permission.User
@@ -15,5 +16,11 @@ import top.fatweb.api.entity.permission.User
interface UserMapper : BaseMapper<User> {
fun getOneWithPowerInfoByUsername(@Param("username")username: String): User?
fun selectPage(page: IPage<Long>, searchValue: String?, searchRegex: Boolean): IPage<Long>
fun getWithRoleInfoByList(userIds: List<Long>): List<User>?
fun getOneWithRoleInfo(id: Long): User?
fun getListWithRoleInfo(): List<User>
}

View File

@@ -0,0 +1,42 @@
package top.fatweb.api.param.authentication
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import java.time.LocalDateTime
@Schema(description = "用户添加请求参数")
data class UserAddParam(
@Schema(description = "用户名")
@field:NotBlank(message = "Username can not be blank")
val username: String?,
@Schema(description = "密码(为空自动生成随机密码)")
val password: String?,
@Schema(description = "锁定")
val locking: Boolean = false,
@Schema(description = "过期时间")
val expiration: LocalDateTime?,
@Schema(description = "认证过期时间")
val credentialsExpiration: LocalDateTime?,
@Schema(description = "启用")
val enable: Boolean = true,
@Schema(description = "昵称")
val nickname: String?,
@Schema(description = "头像")
val avatar: String?,
@Schema(description = "邮箱")
val email: String?,
@Schema(description = "角色 ID 列表")
val roleIds: List<Long>?,
@Schema(description = "用户组 ID 列表")
val groupIds: List<Long>?
)

View File

@@ -0,0 +1,15 @@
package top.fatweb.api.param.authentication
import io.swagger.v3.oas.annotations.media.Schema
/**
* User delete param
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户删除请求参数")
data class UserDeleteParam(
@Schema(description = "用户 ID 列表")
val ids: List<Long>
)

View File

@@ -0,0 +1,19 @@
package top.fatweb.api.param.authentication
import io.swagger.v3.oas.annotations.media.Schema
import top.fatweb.api.param.PageSortParam
/**
* User get param
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户查询请求参数")
data class UserGetParam(
@Schema(description = "查询内容")
val searchValue: String? = null,
@Schema(description = "查询使用正则表达式", allowableValues = ["true", "false"])
val searchRegex: Boolean = false,
) : PageSortParam()

View File

@@ -0,0 +1,42 @@
package top.fatweb.api.param.authentication
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotNull
import java.time.LocalDateTime
@Schema(description = "用户更新请求参数")
data class UserUpdateParam(
@Schema(description = "用户 ID")
@field:NotNull(message = "ID can not be null")
val id: Long?,
@Schema(description = "用户名")
val username: String?,
@Schema(description = "锁定")
val locking: Boolean = false,
@Schema(description = "过期时间")
val expiration: LocalDateTime?,
@Schema(description = "认证过期时间")
val credentialsExpiration: LocalDateTime?,
@Schema(description = "启用")
val enable: Boolean = true,
@Schema(description = "昵称")
val nickname: String?,
@Schema(description = "头像")
val avatar: String?,
@Schema(description = "邮箱")
val email: String?,
@Schema(description = "角色 ID 列表")
val roleIds: List<Long>?,
@Schema(description = "用户组 ID 列表")
val groupIds: List<Long>?
)

View File

@@ -2,6 +2,14 @@ package top.fatweb.api.service.permission
import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.api.entity.permission.User
import top.fatweb.api.param.authentication.UserAddParam
import top.fatweb.api.param.authentication.UserDeleteParam
import top.fatweb.api.param.authentication.UserGetParam
import top.fatweb.api.param.authentication.UserUpdateParam
import top.fatweb.api.vo.PageVo
import top.fatweb.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.api.vo.permission.UserWithPowerInfoVo
import top.fatweb.api.vo.permission.UserWithRoleInfoVo
/**
* User service interface
@@ -12,7 +20,19 @@ import top.fatweb.api.entity.permission.User
interface IUserService : IService<User> {
fun getUserWithPower(username: String): User?
fun getInfo(): User?
fun getInfo(): UserWithPowerInfoVo?
fun getList(): List<User>
fun getPage(userGetParam: UserGetParam?): PageVo<UserWithRoleInfoVo>
fun getOne(id: Long): UserWithRoleInfoVo?
fun getList(): List<UserWithRoleInfoVo>
fun add(userAddParam: UserAddParam): UserWithPasswordRoleInfoVo?
fun update(userUpdateParam: UserUpdateParam): UserWithRoleInfoVo?
fun deleteOne(id: Long)
fun delete(userDeleteParam: UserDeleteParam)
}

View File

@@ -1,11 +1,28 @@
package top.fatweb.api.service.permission.impl
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.baomidou.mybatisplus.extension.plugins.pagination.Page
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import top.fatweb.api.converter.permission.UserConverter
import top.fatweb.api.entity.permission.User
import top.fatweb.api.entity.permission.UserGroup
import top.fatweb.api.entity.permission.UserInfo
import top.fatweb.api.entity.permission.UserRole
import top.fatweb.api.mapper.permission.UserMapper
import top.fatweb.api.param.authentication.UserAddParam
import top.fatweb.api.param.authentication.UserDeleteParam
import top.fatweb.api.param.authentication.UserGetParam
import top.fatweb.api.param.authentication.UserUpdateParam
import top.fatweb.api.service.permission.*
import top.fatweb.api.util.PageUtil
import top.fatweb.api.util.StrUtil
import top.fatweb.api.util.WebUtil
import top.fatweb.api.vo.PageVo
import top.fatweb.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.api.vo.permission.UserWithRoleInfoVo
/**
* User service implement
@@ -15,10 +32,14 @@ import top.fatweb.api.util.WebUtil
*/
@Service
class UserServiceImpl(
private val passwordEncoder: PasswordEncoder,
private val userInfoService: IUserInfoService,
private val moduleService: IModuleService,
private val menuService: IMenuService,
private val elementService: IElementService,
private val operationService: IOperationService
private val operationService: IOperationService,
private val userRoleService: IUserRoleService,
private val userGroupService: IUserGroupService
) : ServiceImpl<UserMapper, User>(), IUserService {
override fun getUserWithPower(username: String): User? {
val user = baseMapper.getOneWithPowerInfoByUsername(username)
@@ -34,7 +55,152 @@ class UserServiceImpl(
return user
}
override fun getInfo() = WebUtil.getLoginUsername()?.let { getUserWithPower(it) } ?: let { null }
override fun getInfo() = WebUtil.getLoginUsername()
?.let { username -> getUserWithPower(username)?.let { UserConverter.userToUserWithPowerInfoVo(it) } }
override fun getList() = baseMapper.getListWithRoleInfo()
override fun getPage(userGetParam: UserGetParam?): PageVo<UserWithRoleInfoVo> {
val userIdsPage = Page<Long>(userGetParam?.currentPage ?: 1, userGetParam?.pageSize ?: 20)
PageUtil.setPageSort(userGetParam, userIdsPage)
val userIdsIPage =
baseMapper.selectPage(userIdsPage, userGetParam?.searchValue, userGetParam?.searchRegex ?: false)
val userPage = Page<User>(userIdsIPage.current, userIdsIPage.size, userIdsIPage.total)
if (userIdsIPage.total > 0) {
userPage.setRecords(baseMapper.getWithRoleInfoByList(userIdsIPage.records))
}
return UserConverter.userPageToUserWithRoleInfoPageVo(userPage)
}
override fun getOne(id: Long) =
baseMapper.getOneWithRoleInfo(id)?.let { UserConverter.userToUserWithRoleInfoVo(it) }
override fun getList() = baseMapper.getListWithRoleInfo().map { UserConverter.userToUserWithRoleInfoVo(it) }
@Transactional
override fun add(userAddParam: UserAddParam): UserWithPasswordRoleInfoVo? {
val rawPassword =
if (userAddParam.password.isNullOrBlank()) StrUtil.getRandomPassword(10) else userAddParam.password
val user = UserConverter.userAddParamToUser(userAddParam)
user.apply {
password = passwordEncoder.encode(rawPassword)
}
if (baseMapper.insert(user) == 1) {
user.userInfo?.let { userInfoService.save(it.apply { userId = user.id }) }
if (!user.roles.isNullOrEmpty()) {
userRoleService.saveBatch(user.roles!!.map {
UserRole().apply {
userId = user.id
roleId = it.id
}
})
}
if (!user.groups.isNullOrEmpty()) {
userGroupService.saveBatch(user.groups!!.map {
UserGroup().apply {
userId = user.id
groupId = it.id
}
})
}
user.password = rawPassword
return UserConverter.userToUserWithPasswordRoleInfoVo(user)
}
return null
}
@Transactional
override fun update(userUpdateParam: UserUpdateParam): UserWithRoleInfoVo? {
val user = UserConverter.userUpdateParamToUser(userUpdateParam)
val oldRoleList = userRoleService.list(
KtQueryWrapper(UserRole()).select(UserRole::roleId).eq(UserRole::userId, userUpdateParam.id)
).map { it.roleId }
val addRoleIds = HashSet<Long>()
val removeRoleIds = HashSet<Long>()
userUpdateParam.roleIds?.forEach { addRoleIds.add(it) }
oldRoleList.forEach {
if (it != null) {
removeRoleIds.add(it)
}
}
removeRoleIds.removeAll(addRoleIds)
oldRoleList.toSet().let { addRoleIds.removeAll(it) }
val oldGroupList = userGroupService.list(
KtQueryWrapper(UserGroup()).select(UserGroup::groupId).eq(UserGroup::userId, userUpdateParam.id)
).map { it.groupId }
val addGroupIds = HashSet<Long>()
val removeGroupIds = HashSet<Long>()
userUpdateParam.groupIds?.forEach { addGroupIds.add(it) }
oldGroupList.forEach {
if (it != null) {
removeGroupIds.add(it)
}
}
removeGroupIds.removeAll(addGroupIds)
oldGroupList.toSet().let { addGroupIds.removeAll(it) }
baseMapper.updateById(user)
user.userInfo?.let { userInfo ->
userInfoService.getOne(
KtQueryWrapper(UserInfo()).select(UserInfo::id).eq(UserInfo::userId, userUpdateParam.id)
)?.let {
userInfo.id = it.id
userInfoService.updateById(userInfo)
}
}
removeRoleIds.forEach {
userRoleService.remove(
KtQueryWrapper(UserRole()).eq(
UserRole::userId, userUpdateParam.id
).eq(UserRole::roleId, it)
)
}
addRoleIds.forEach {
userRoleService.save(UserRole().apply {
userId = userUpdateParam.id
roleId = it
})
}
removeGroupIds.forEach {
userGroupService.remove(
KtQueryWrapper(UserGroup()).eq(
UserGroup::userId, userUpdateParam.id
).eq(UserGroup::groupId, it)
)
}
addGroupIds.forEach {
userGroupService.save(UserGroup().apply {
userId = userUpdateParam.id
groupId = it
})
}
return UserConverter.userToUserWithRoleInfoVo(user)
}
override fun deleteOne(id: Long) {
this.delete(UserDeleteParam(listOf(id)))
}
override fun delete(userDeleteParam: UserDeleteParam) {
baseMapper.deleteBatchIds(userDeleteParam.ids)
userInfoService.remove(KtQueryWrapper(UserInfo()).`in`(UserInfo::userId, userDeleteParam.ids))
userRoleService.remove(KtQueryWrapper(UserRole()).`in`(UserRole::userId, userDeleteParam.ids))
userGroupService.remove(KtQueryWrapper(UserGroup()).`in`(UserGroup::userId, userDeleteParam.ids))
}
}

View File

@@ -0,0 +1,63 @@
package top.fatweb.api.vo.permission
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import io.swagger.v3.oas.annotations.media.Schema
import java.time.LocalDateTime
/**
* User with role information value object
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户密码角色信息返回参数")
data class UserWithPasswordRoleInfoVo(
@JsonSerialize(using = ToStringSerializer::class)
val id: Long?,
@Schema(description = "用户名", example = "User")
val username: String?,
@Schema(description = "密码")
val password: String?,
@Schema(description = "是否锁定", example = "false")
val locking: Boolean?,
@Schema(description = "过期时间", example = "1900-01-01T00:00:00.000Z")
val expiration: LocalDateTime?,
@Schema(description = "认证过期时间", example = "1900-01-01T00:00:00.000Z")
val credentialsExpiration: LocalDateTime?,
@Schema(description = "是否启用", example = "true")
val enable: Boolean?,
@Schema(description = "当前登录时间", example = "1900-01-01T00:00:00.000Z")
val currentLoginTime: LocalDateTime?,
@Schema(description = "当前登录 IP", example = "1.1.1.1")
val currentLoginIp: String?,
@Schema(description = "最后登录时间", example = "1900-01-01T00:00:00.000Z")
val lastLoginTime: LocalDateTime?,
@Schema(description = "最后登录 IP", example = "1.1.1.1")
val lastLoginIp: String?,
@Schema(description = "创建时间", example = "1900-01-01T00:00:00.000Z")
val createTime: LocalDateTime?,
@Schema(description = "修改时间", example = "1900-01-01T00:00:00.000Z")
val updateTime: LocalDateTime?,
@Schema(description = "用户资料")
val userInfo: UserInfoVo?,
@Schema(description = "角色列表")
val roles: List<RoleVo>?,
@Schema(description = "用户组列表")
val groups: List<GroupVo>?
)