Finish sign api. Optimize code.

This commit is contained in:
2023-12-26 18:17:03 +08:00
parent 15f631971c
commit e45769e52a
43 changed files with 570 additions and 107 deletions

View File

@@ -75,7 +75,9 @@ class SecurityConfig(
"/swagger-ui.html",
"/favicon.ico",
"/login",
"/register"
"/register",
"/forget",
"/retrieve"
).anonymous()
// Authentication required
.anyRequest().authenticated()

View File

@@ -9,9 +9,7 @@ import org.springframework.web.bind.annotation.RequestBody
import top.fatweb.api.annotation.BaseController
import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult
import top.fatweb.api.param.permission.LoginParam
import top.fatweb.api.param.permission.RegisterParam
import top.fatweb.api.param.permission.VerifyParam
import top.fatweb.api.param.permission.*
import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.util.WebUtil
import top.fatweb.api.vo.permission.LoginVo
@@ -49,7 +47,7 @@ class AuthenticationController(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Operation(summary = "发送验证邮")
@Operation(summary = "发送验证邮")
@PostMapping("/resend")
fun resend(): ResponseResult<Nothing> {
authenticationService.resend()
@@ -58,12 +56,12 @@ class AuthenticationController(
}
/**
* Verify
* Verify email
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Operation(summary = "验证")
@Operation(summary = "验证邮箱")
@PostMapping("/verify")
fun verify(@Valid @RequestBody verifyParam: VerifyParam): ResponseResult<Nothing> {
authenticationService.verify(verifyParam)
@@ -71,6 +69,34 @@ class AuthenticationController(
return ResponseResult.success(ResponseCode.PERMISSION_VERIFY_SUCCESS)
}
/**
* Forget password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Operation(summary = "忘记密码")
@PostMapping("/forget")
fun forget(request: HttpServletRequest, @Valid @RequestBody forgetParam: ForgetParam): ResponseResult<Nothing> {
authenticationService.forget(request, forgetParam)
return ResponseResult.success(ResponseCode.PERMISSION_FORGET_SUCCESS)
}
/**
* Retrieve password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Operation(summary = "找回密码")
@PostMapping("/retrieve")
fun retrieve(request: HttpServletRequest, @Valid @RequestBody retrieveParam: RetrieveParam): ResponseResult<Nothing> {
authenticationService.retrieve(request, retrieveParam)
return ResponseResult.success(ResponseCode.PERMISSION_RETRIEVE_SUCCESS)
}
/**
* Login
*

View File

@@ -21,6 +21,8 @@ enum class ResponseCode(val code: Int) {
PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4),
PERMISSION_RESEND_SUCCESS(BusinessCode.PERMISSION, 5),
PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 6),
PERMISSION_FORGET_SUCCESS(BusinessCode.PERMISSION, 7),
PERMISSION_RETRIEVE_SUCCESS(BusinessCode.PERMISSION, 8),
PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50),
PERMISSION_USERNAME_NOT_FOUND(BusinessCode.PERMISSION, 51),
@@ -37,6 +39,9 @@ enum class ResponseCode(val code: Int) {
PERMISSION_NO_VERIFICATION_REQUIRED(BusinessCode.PERMISSION, 62),
PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 63),
PERMISSION_ACCOUNT_NEED_INIT(BusinessCode.PERMISSION, 64),
PERMISSION_USER_NOT_FOUND(BusinessCode.PERMISSION, 65),
PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 66),
PERMISSION_ACCOUNT_NEED_RESET_PASSWORD(BusinessCode.PERMISSION, 67),
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),

View File

@@ -47,7 +47,7 @@ class User() : Serializable {
var password: String? = null
/**
* Verify
* Verify email
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
@@ -55,6 +55,15 @@ class User() : Serializable {
@TableField("verify")
var verify: String? = null
/**
* Forget password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("forget")
var forget: String? = null
/**
* Locking
*
@@ -242,6 +251,6 @@ class User() : Serializable {
var operations: List<Operation>? = null
override fun toString(): String {
return "User(id=$id, username=$username, password=$password, locking=$locking, expiration=$expiration, credentialsExpiration=$credentialsExpiration, enable=$enable, currentLoginTime=$currentLoginTime, currentLoginIp=$currentLoginIp, lastLoginTime=$lastLoginTime, lastLoginIp=$lastLoginIp, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, userInfo=$userInfo, roles=$roles, groups=$groups, modules=$modules, menus=$menus, funcs=$funcs, operations=$operations)"
return "User(id=$id, username=$username, password=$password, verify=$verify, forget=$forget, locking=$locking, expiration=$expiration, credentialsExpiration=$credentialsExpiration, enable=$enable, currentLoginTime=$currentLoginTime, currentLoginIp=$currentLoginIp, lastLoginTime=$lastLoginTime, lastLoginIp=$lastLoginIp, createTime=$createTime, updateTime=$updateTime, deleted=$deleted, version=$version, userInfo=$userInfo, roles=$roles, groups=$groups, modules=$modules, menus=$menus, funcs=$funcs, operations=$operations)"
}
}

View File

@@ -0,0 +1,9 @@
package top.fatweb.api.exception
/**
* Account need reset password exception
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class AccountNeedResetPasswordException : RuntimeException("Account need reset password")

View File

@@ -0,0 +1,3 @@
package top.fatweb.api.exception
class RetrieveCodeErrorOrExpiredException : RuntimeException("Retrieve code error or expired")

View File

@@ -0,0 +1,3 @@
package top.fatweb.api.exception
class UserNotFoundException : RuntimeException("User not found")

View File

@@ -121,6 +121,11 @@ class ExceptionHandler {
ResponseResult.fail(ResponseCode.PERMISSION_ACCESS_DENIED, "Access Denied", null)
}
is UserNotFoundException -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_USER_NOT_FOUND, e.localizedMessage, null)
}
is NoVerificationRequiredException -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_NO_VERIFICATION_REQUIRED, e.localizedMessage, null)
@@ -136,6 +141,16 @@ class ExceptionHandler {
ResponseResult.fail(ResponseCode.PERMISSION_ACCOUNT_NEED_INIT, e.localizedMessage, null)
}
is RetrieveCodeErrorOrExpiredException -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_RETRIEVE_CODE_ERROR_OR_EXPIRED, e.localizedMessage, null)
}
is AccountNeedResetPasswordException -> {
logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_ACCOUNT_NEED_RESET_PASSWORD, e.localizedMessage, null)
}
is BadSqlGrammarException -> {
logger.debug(e.localizedMessage, e)

View File

@@ -3,6 +3,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.Group
/**
@@ -26,7 +27,11 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0
* @see IPage
*/
fun selectPage(page: IPage<Long>, searchName: String?, searchRegex: Boolean): IPage<Long>
fun selectPage(
page: IPage<Long>,
@Param("searchName") searchName: String?,
@Param("searchRegex") searchRegex: Boolean
): IPage<Long>
/**
* Select group with role list by list of group IDs
@@ -37,7 +42,7 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0
* @see Group
*/
fun selectListWithRoleByIds(groupIds: List<Long>): List<Group>?
fun selectListWithRoleByIds(@Param("groupIds") groupIds: List<Long>): List<Group>?
/**
* Select one group by ID
@@ -48,5 +53,5 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0
* @see Group
*/
fun selectOneById(id: Long): Group?
fun selectOneById(@Param("id") id: Long): Group?
}

View File

@@ -3,6 +3,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.Role
/**
@@ -26,7 +27,11 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0
* @see IPage
*/
fun selectPage(page: IPage<Long>, searchName: String?, searchRegex: Boolean): IPage<Long>
fun selectPage(
page: IPage<Long>,
@Param("searchName") searchName: String?,
@Param("searchRegex") searchRegex: Boolean
): IPage<Long>
/**
* Select role with power list by list of role IDs
@@ -37,7 +42,7 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0
* @see Role
*/
fun selectListWithPowerByIds(roleIds: List<Long>): List<Role>?
fun selectListWithPowerByIds(@Param("roleIds") roleIds: List<Long>): List<Role>?
/**
* Select one role by ID
@@ -48,5 +53,5 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0
* @see Role
*/
fun selectOneById(id: Long): Role?
fun selectOneById(@Param("id") id: Long): Role?
}

View File

@@ -39,7 +39,12 @@ interface UserMapper : BaseMapper<User> {
* @since 1.0.0
* @see IPage
*/
fun selectPage(page: IPage<Long>, searchType: String, searchValue: String?, searchRegex: Boolean): IPage<Long>
fun selectPage(
page: IPage<Long>,
@Param("searchType") searchType: String,
@Param("searchValue") searchValue: String?,
@Param("searchRegex") searchRegex: Boolean
): IPage<Long>
/**
* Select user with role and information list by list of user IDs
@@ -50,7 +55,7 @@ interface UserMapper : BaseMapper<User> {
* @since 1.0.0
* @see User
*/
fun selectListWithRoleInfoByIds(userIds: List<Long>): List<User>
fun selectListWithRoleInfoByIds(@Param("userIds") userIds: List<Long>): List<User>
/**
* Select one user by ID
@@ -61,7 +66,7 @@ interface UserMapper : BaseMapper<User> {
* @since 1.0.0
* @see User
*/
fun selectOneWithRoleInfoById(id: Long): User?
fun selectOneWithRoleInfoById(@Param("id") id: Long): User?
/**
* Select all user with information list
@@ -81,7 +86,7 @@ interface UserMapper : BaseMapper<User> {
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun selectIdsWithRoleIds(roleIds: List<Long>): List<Long>
fun selectIdsWithRoleIds(@Param("roleIds") roleIds: List<Long>): List<Long>
/**
* Select user IDs list by list of group IDs
@@ -91,5 +96,5 @@ interface UserMapper : BaseMapper<User> {
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun selectIdsWithGroupIds(groupIds: List<Long>): List<Long>
fun selectIdsWithGroupIds(@Param("groupIds") groupIds: List<Long>): List<Long>
}

View File

@@ -3,6 +3,7 @@ package top.fatweb.api.mapper.system
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.system.SysLog
import java.time.LocalDateTime
@@ -34,10 +35,10 @@ interface SysLogMapper : BaseMapper<SysLog> {
*/
fun selectPage(
page: IPage<SysLog>,
logType: List<String>?,
requestMethod: List<String>?,
searchRequestUrl: String?,
searchStartTime: LocalDateTime?,
searchEndTime: LocalDateTime?
@Param("logType") logType: List<String>?,
@Param("requestMethod") requestMethod: List<String>?,
@Param("searchRequestUrl") searchRequestUrl: String?,
@Param("searchStartTime") searchStartTime: LocalDateTime?,
@Param("searchEndTime") searchEndTime: LocalDateTime?
): IPage<SysLog>
}

View File

@@ -16,7 +16,7 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "分页页码", example = "1", defaultValue = "1")
@Schema(description = "分页页码", defaultValue = "1", example = "1")
@field:Min(1, message = "Pagination page number must be a positive integer")
var currentPage: Long = 1
@@ -26,7 +26,7 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "分页大小", example = "20", defaultValue = "20")
@Schema(description = "分页大小", defaultValue = "20", example = "20")
@field:Min(1, message = "The number of data per page must be a positive integer")
var pageSize: Long = 20
@@ -45,6 +45,6 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "排序方式", example = "desc", allowableValues = ["desc", "asc"])
@Schema(description = "排序方式", allowableValues = ["desc", "asc"], defaultValue = "desc", example = "desc")
var sortOrder: String? = null
}

View File

@@ -0,0 +1,25 @@
package top.fatweb.api.param.permission
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Pattern
/**
* Forget password parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "忘记密码请求参数")
data class ForgetParam(
/**
* Email
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "邮箱", required = true, example = "user@email.com")
@field:NotBlank(message = "Email can not be blank")
@field:Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?
)

View File

@@ -17,7 +17,7 @@ data class LoginParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "账户", example = "test", required = true)
@Schema(description = "账户", required = true, example = "test")
@field:NotBlank(message = "Account can not be blank")
val account: String?,
@@ -27,7 +27,7 @@ data class LoginParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "密码", example = "test123456", required = true)
@Schema(description = "密码", required = true)
@field:NotBlank(message = "Password can not be blank")
val password: String?
)

View File

@@ -19,7 +19,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户名", example = "abc", required = true)
@Schema(description = "用户名", required = true, example = "abc")
@field:NotBlank(message = "Username can not be blank")
@field:Pattern(regexp = "[a-zA-Z-_][0-9a-zA-Z-_]{2,38}", message = "Illegal username")
val username: String?,
@@ -30,7 +30,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "邮箱", example = "guest@fatweb.top", required = true)
@Schema(description = "邮箱", required = true, example = "user@email.com")
@field:NotBlank(message = "Email can not be blank")
@field:Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?,
@@ -41,7 +41,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "密码", example = "test123456", required = true)
@Schema(description = "密码", required = true)
@field:NotBlank(message = "Password can not be blank")
@field:Size(min = 10, max = 30)
val password: String?

View File

@@ -0,0 +1,35 @@
package top.fatweb.api.param.permission
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
/**
* Retrieve password parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "找回密码请求参数")
data class RetrieveParam(
/**
* Code
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "验证码", required = true)
@field:NotBlank(message = "Code can not be blank")
val code: String?,
/**
* New password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "新密码")
@field:NotBlank(message = "New password can not be blank")
@field:Size(min = 10, max = 30)
val password: String?
)

View File

@@ -4,12 +4,12 @@ import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
/**
* Verify parameters
* Verify email parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "验证请求参数")
@Schema(description = "验证邮箱请求参数")
data class VerifyParam(
/**
* Code
@@ -17,7 +17,7 @@ data class VerifyParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "验证码", example = "9c4b8199-1dbe-4f6f-96a5-fe1d75cc6a65", required = true)
@Schema(description = "验证码", required = true)
@field:NotBlank(message = "Code can not be blank")
val code: String?,

View File

@@ -17,7 +17,7 @@ data class GroupAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户组名称")
@Schema(description = "用户组名称", required = true, example = "Group_1")
@field:NotBlank(message = "Name can not be blank")
val name: String?,
@@ -27,7 +27,7 @@ data class GroupAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**

View File

@@ -16,6 +16,6 @@ data class GroupDeleteParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户组 ID 列表")
@Schema(description = "用户组 ID 列表", required = true)
val ids: List<Long>
)

View File

@@ -18,7 +18,7 @@ data class GroupGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询用户组名称")
@Schema(description = "查询用户组名称", example = "Group_1")
val searchName: String?,
/**
@@ -27,6 +27,11 @@ data class GroupGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询使用正则表达式", allowableValues = ["true", "false"], defaultValue = "false")
@Schema(
description = "查询使用正则表达式",
allowableValues = ["true", "false"],
defaultValue = "false",
example = "false"
)
val searchRegex: Boolean = false,
) : PageSortParam()

View File

@@ -18,7 +18,7 @@ data class GroupUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户组 ID")
@Schema(description = "用户组 ID", required = true)
@field:NotNull(message = "ID can not be null")
val id: Long?,
@@ -28,7 +28,7 @@ data class GroupUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户组名称")
@Schema(description = "用户组名称", required = true, example = "Group_1")
@field:NotBlank(message = "Name can not be blank")
val name: String?,
@@ -38,7 +38,7 @@ data class GroupUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**

View File

@@ -17,7 +17,7 @@ data class GroupUpdateStatusParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户组 ID")
@Schema(description = "用户组 ID", required = true)
@field:NotNull(message = "ID can not be null")
val id: Long?,
@@ -27,6 +27,6 @@ data class GroupUpdateStatusParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true
)

View File

@@ -17,7 +17,7 @@ data class RoleAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "角色名称")
@Schema(description = "角色名称", required = true, example = "Role_1")
@field:NotBlank(message = "Name can not be blank")
val name: String?,
@@ -27,7 +27,7 @@ data class RoleAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**

View File

@@ -16,6 +16,6 @@ data class RoleDeleteParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "角色 ID 列表")
@Schema(description = "角色 ID 列表", required = true)
val ids: List<Long>
)

View File

@@ -18,7 +18,7 @@ data class RoleGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询角色名称")
@Schema(description = "查询角色名称", example = "Role_1")
val searchName: String?,
/**
@@ -27,6 +27,11 @@ data class RoleGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询使用正则表达式", allowableValues = ["true", "false"], defaultValue = "false")
@Schema(
description = "查询使用正则表达式",
allowableValues = ["true", "false"],
defaultValue = "false",
example = "false"
)
val searchRegex: Boolean = false,
) : PageSortParam()

View File

@@ -18,7 +18,7 @@ data class RoleUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "角色 ID")
@Schema(description = "角色 ID", required = true)
@field:NotNull(message = "Role id can not be null")
val id: Long?,
@@ -28,7 +28,7 @@ data class RoleUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "角色名称")
@Schema(description = "角色名称", required = true, example = "Role_1")
@field:NotBlank(message = "Name can not be blank")
val name: String?,
@@ -38,7 +38,7 @@ data class RoleUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**

View File

@@ -17,7 +17,7 @@ data class RoleUpdateStatusParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "角色 ID")
@Schema(description = "角色 ID", required = true)
@field:NotNull(message = "Role id can not be null")
val id: Long?,
@@ -27,6 +27,6 @@ data class RoleUpdateStatusParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true
)

View File

@@ -19,7 +19,7 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户名")
@Schema(description = "用户名", required = true, example = "User_1")
@field:NotBlank(message = "Username can not be blank")
val username: String?,
@@ -38,7 +38,7 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "是否已验证")
@Schema(description = "是否已验证", allowableValues = ["true", "false"], defaultValue = "false", example = "false")
val verified: Boolean = false,
/**
@@ -47,7 +47,7 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "锁定", allowableValues = ["true", "false"], defaultValue = "false")
@Schema(description = "锁定", allowableValues = ["true", "false"], defaultValue = "false", example = "false")
val locking: Boolean = false,
/**
@@ -57,7 +57,7 @@ data class UserAddParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "过期时间")
@Schema(description = "过期时间", example = "1900-01-01T00:00:00.000Z")
val expiration: LocalDateTime?,
/**
@@ -67,7 +67,7 @@ data class UserAddParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "认证过期时间")
@Schema(description = "认证过期时间", example = "1900-01-01T00:00:00.000Z")
val credentialsExpiration: LocalDateTime?,
/**
@@ -76,7 +76,7 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**
@@ -85,7 +85,7 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "昵称")
@Schema(description = "昵称", required = true, example = "Nickname_1")
@field:NotBlank(message = "Nickname can not be blank")
val nickname: String?,
@@ -104,7 +104,8 @@ data class UserAddParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "邮箱")
@Schema(description = "邮箱", required = true, example = "user@email.com")
@NotBlank(message = "Email can not be blank")
@Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?,

View File

@@ -16,6 +16,6 @@ data class UserDeleteParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户 ID 列表")
@Schema(description = "用户 ID 列表", required = true)
val ids: List<Long>
)

View File

@@ -18,7 +18,12 @@ data class UserGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "搜索类型", allowableValues = ["ALL", "ID", "USERNAME", "NICKNAME", "EMAIL"], defaultValue = "ALL")
@Schema(
description = "搜索类型",
allowableValues = ["ALL", "ID", "USERNAME", "NICKNAME", "EMAIL"],
defaultValue = "ALL",
example = "ALL"
)
val searchType: String = "ALL",
/**
@@ -27,7 +32,7 @@ data class UserGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询内容")
@Schema(description = "查询内容", example = "User_1")
val searchValue: String?,
/**
@@ -36,6 +41,11 @@ data class UserGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "查询使用正则表达式", allowableValues = ["true", "false"], defaultValue = "false")
@Schema(
description = "查询使用正则表达式",
allowableValues = ["true", "false"],
defaultValue = "false",
example = "false"
)
val searchRegex: Boolean = false,
) : PageSortParam()

View File

@@ -1,6 +1,7 @@
package top.fatweb.api.param.permission.user
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import java.time.LocalDateTime
@@ -19,7 +20,7 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户 ID")
@Schema(description = "用户 ID", required = true)
@field:NotNull(message = "ID can not be null")
val id: Long?,
@@ -29,7 +30,8 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户名")
@Schema(description = "用户名", required = true, example = "User_1")
@field:NotBlank(message = "Username can not be blank")
val username: String?,
/**
@@ -38,7 +40,7 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "是否已验证")
@Schema(description = "是否已验证", allowableValues = ["true", "false"], defaultValue = "false", example = "false")
val verified: Boolean = false,
/**
@@ -47,7 +49,7 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "锁定", allowableValues = ["true", "false"], defaultValue = "false")
@Schema(description = "锁定", allowableValues = ["true", "false"], defaultValue = "false", example = "false")
val locking: Boolean = false,
/**
@@ -57,7 +59,7 @@ data class UserUpdateParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "过期时间")
@Schema(description = "过期时间", example = "1900-01-01T00:00:00.000Z")
val expiration: LocalDateTime?,
/**
@@ -67,7 +69,7 @@ data class UserUpdateParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "认证过期时间")
@Schema(description = "认证过期时间", example = "1900-01-01T00:00:00.000Z")
val credentialsExpiration: LocalDateTime?,
/**
@@ -76,7 +78,7 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true")
@Schema(description = "启用", allowableValues = ["true", "false"], defaultValue = "true", example = "true")
val enable: Boolean = true,
/**
@@ -85,7 +87,8 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "昵称")
@Schema(description = "昵称", required = true, example = "Nickname_1")
@field:NotBlank(message = "Nickname can not be blank")
val nickname: String?,
/**
@@ -103,7 +106,8 @@ data class UserUpdateParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "邮箱")
@Schema(description = "邮箱", required = true, example = "user@email.com")
@NotBlank(message = "Email can not be blank")
@Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?,

View File

@@ -19,7 +19,7 @@ data class UserUpdatePasswordParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "用户 ID")
@Schema(description = "用户 ID", required = true)
@field:NotNull(message = "ID can not be null")
val id: Long?,
@@ -29,7 +29,7 @@ data class UserUpdatePasswordParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "新密码")
@Schema(description = "新密码", required = true)
@field:NotBlank(message = "Password can not be blank")
val password: String?,
@@ -40,6 +40,6 @@ data class UserUpdatePasswordParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "认证过期时间")
@Schema(description = "认证过期时间", example = "1900-01-01T00:00:00.000Z")
val credentialsExpiration: LocalDateTime?
)

View File

@@ -2,6 +2,7 @@ package top.fatweb.api.param.system
import com.baomidou.mybatisplus.annotation.EnumValue
import com.fasterxml.jackson.annotation.JsonValue
import io.swagger.v3.oas.annotations.media.Schema
/**
* Get active information parameters
@@ -10,6 +11,18 @@ import com.fasterxml.jackson.annotation.JsonValue
* @since 1.0.0
*/
data class ActiveInfoGetParam(
/**
* Scope
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(
description = "范围",
allowableValues = ["WEAK", "MONTH", "QUARTER", "YEAR", "TWO_YEARS", "THREE_YEARS", "FIVE_YEARS", "ALL"],
defaultValue = "WEAK",
example = "WEAK"
)
val scope: Scope = Scope.WEAK
) {
enum class Scope(@field:EnumValue @field:JsonValue val code: String) {

View File

@@ -17,7 +17,7 @@ data class MailSendParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "接收者")
@Schema(description = "接收者", required = true, example = "user@email.com")
@field:NotBlank
val to: String?
)

View File

@@ -2,6 +2,7 @@ package top.fatweb.api.param.system
import com.baomidou.mybatisplus.annotation.EnumValue
import com.fasterxml.jackson.annotation.JsonValue
import io.swagger.v3.oas.annotations.media.Schema
/**
* Get online information parameters
@@ -10,6 +11,18 @@ import com.fasterxml.jackson.annotation.JsonValue
* @since 1.0.0
*/
data class OnlineInfoGetParam(
/**
* Scope
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(
description = "范围",
allowableValues = ["WEAK", "MONTH", "QUARTER", "YEAR", "TWO_YEARS", "THREE_YEARS", "FIVE_YEARS", "ALL"],
defaultValue = "WEAK",
example = "WEAK"
)
val scope: Scope = Scope.WEAK
) {
enum class Scope(@field:EnumValue @field:JsonValue val code: String) {

View File

@@ -20,7 +20,7 @@ data class SysLogGetParam(
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "类型过滤(多个使用逗号分隔)", example = "INFO", allowableValues = ["INFO", "ERROR"])
@Schema(description = "类型过滤(多个使用逗号分隔)", allowableValues = ["INFO", "ERROR"], example = "INFO")
val logType: String?,
/**
@@ -31,8 +31,8 @@ data class SysLogGetParam(
*/
@Schema(
description = "请求方式过滤(多个使用逗号分隔)",
example = "GET,POST",
allowableValues = ["GET", "POST", "PUT", "PATCH", "DELETE", "DELETE", "OPTIONS"]
allowableValues = ["GET", "POST", "PUT", "PATCH", "DELETE", "DELETE", "OPTIONS"],
example = "GET,POST"
)
val requestMethod: String?,
@@ -63,7 +63,7 @@ data class SysLogGetParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "查询开始时间")
@Schema(description = "查询开始时间", example = "1900-01-01T00:00:00.000Z")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
val searchStartTime: LocalDateTime?,
@@ -74,7 +74,7 @@ data class SysLogGetParam(
* @since 1.0.0
* @see LocalDateTime
*/
@Schema(description = "查询结束时间")
@Schema(description = "查询结束时间", example = "1900-01-01T00:00:00.000Z")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
val searchEndTime: LocalDateTime?
) : PageSortParam()

View File

@@ -2,9 +2,7 @@ package top.fatweb.api.service.permission
import jakarta.servlet.http.HttpServletRequest
import top.fatweb.api.entity.permission.User
import top.fatweb.api.param.permission.LoginParam
import top.fatweb.api.param.permission.RegisterParam
import top.fatweb.api.param.permission.VerifyParam
import top.fatweb.api.param.permission.*
import top.fatweb.api.vo.permission.LoginVo
import top.fatweb.api.vo.permission.RegisterVo
import top.fatweb.api.vo.permission.TokenVo
@@ -33,13 +31,29 @@ interface IAuthenticationService {
fun resend()
/**
* Verify
* Verify email
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun verify(verifyParam: VerifyParam)
/**
* Forget password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun forget(request: HttpServletRequest, forgetParam: ForgetParam)
/**
* Retrieve password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
fun retrieve(request: HttpServletRequest, retrieveParam: RetrieveParam)
/**
* Login
*

View File

@@ -1,5 +1,6 @@
package top.fatweb.api.service.permission.impl
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper
import jakarta.servlet.http.HttpServletRequest
import org.apache.velocity.VelocityContext
@@ -17,13 +18,8 @@ import top.fatweb.api.entity.permission.LoginUser
import top.fatweb.api.entity.permission.User
import top.fatweb.api.entity.permission.UserInfo
import top.fatweb.api.entity.system.EventLog
import top.fatweb.api.exception.AccountNeedInitException
import top.fatweb.api.exception.NoVerificationRequiredException
import top.fatweb.api.exception.TokenHasExpiredException
import top.fatweb.api.exception.VerificationCodeErrorOrExpiredException
import top.fatweb.api.param.permission.LoginParam
import top.fatweb.api.param.permission.RegisterParam
import top.fatweb.api.param.permission.VerifyParam
import top.fatweb.api.exception.*
import top.fatweb.api.param.permission.*
import top.fatweb.api.properties.SecurityProperties
import top.fatweb.api.service.api.v1.IAvatarService
import top.fatweb.api.service.permission.IAuthenticationService
@@ -71,7 +67,9 @@ class AuthenticationServiceImpl(
username = registerParam.username
password = passwordEncoder.encode(registerParam.password)
verify =
"${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
"${
LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()
}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
locking = 0
enable = 1
}
@@ -83,7 +81,7 @@ class AuthenticationServiceImpl(
email = registerParam.email
})
sendVerifyMail(user.username!!, "http://localhost:5173/verify?code=${user.verify!!}", registerParam.email!!)
sendVerifyMail(user.username!!, user.verify!!, registerParam.email!!)
return RegisterVo(userId = user.id)
}
@@ -95,21 +93,23 @@ class AuthenticationServiceImpl(
user.verify ?: throw NoVerificationRequiredException()
user.verify =
"${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
"${
LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()
}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
user.updateTime = LocalDateTime.now(ZoneOffset.UTC)
userService.updateById(user)
WebUtil.getLoginUser()?.user?.userInfo?.email?.let {
sendVerifyMail(user.username!!, "http://localhost:5173/verify?code=${user.verify!!}", it)
sendVerifyMail(user.username!!, user.verify!!, it)
} ?: throw AccessDeniedException("Access Denied")
}
private fun sendVerifyMail(username: String, verifyUrl: String, email: String) {
private fun sendVerifyMail(username: String, code: String, email: String) {
val velocityContext = VelocityContext().apply {
put("appName", "氮工具")
put("appUrl", "http://localhost:5173/")
put("username", username)
put("verifyUrl", verifyUrl)
put("verifyUrl", "http://localhost:5173/verify?code=${code}")
}
val template = velocityEngine.getTemplate("templates/email-verify-account-cn.vm")
@@ -147,6 +147,82 @@ class AuthenticationServiceImpl(
)
}
@Transactional
override fun forget(request: HttpServletRequest, forgetParam: ForgetParam) {
val user = userService.getUserWithPowerByAccount(forgetParam.email!!)
user ?: let { throw UserNotFoundException() }
val code = "${
LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()
}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
userService.update(KtUpdateWrapper(User()).eq(User::id, user.id).set(User::forget, code))
sendRetrieveMail(user.username!!, request.remoteAddr, code, forgetParam.email)
}
private fun sendRetrieveMail(username: String, ip: String, code: String, email: String) {
val velocityContext = VelocityContext().apply {
put("appName", "氮工具")
put("appUrl", "http://localhost:5173/")
put("username", username)
put("ipAddress", ip)
put("retrieveUrl", "http://localhost:5173/forget?code=${code}")
}
val template = velocityEngine.getTemplate("templates/email-retrieve-password-cn.vm")
val stringWriter = StringWriter()
template.merge(velocityContext, stringWriter)
MailUtil.sendSimpleMail(
"找回您的密码", stringWriter.toString(), true,
email
)
}
@Transactional
override fun retrieve(request: HttpServletRequest, retrieveParam: RetrieveParam) {
val codeStrings = retrieveParam.code!!.split("-")
if (codeStrings.size != 16) {
throw RetrieveCodeErrorOrExpiredException()
}
try {
if (LocalDateTime.ofInstant(Instant.ofEpochMilli(codeStrings.first().toLong()), ZoneOffset.UTC)
.isBefore(LocalDateTime.now(ZoneOffset.UTC).minusHours(2))
) {
throw RetrieveCodeErrorOrExpiredException()
}
} catch (e: Exception) {
throw RetrieveCodeErrorOrExpiredException()
}
val user = userService.getOne(KtQueryWrapper(User()).eq(User::forget, retrieveParam.code))
?: throw RetrieveCodeErrorOrExpiredException()
val userInfo = userInfoService.getOne(KtQueryWrapper(UserInfo()).eq(UserInfo::userId, user.id))
userService.update(
KtUpdateWrapper(User()).eq(User::id, user.id).set(User::forget, null)
.set(User::password, passwordEncoder.encode(retrieveParam.password!!))
)
sendPasswordChangedMail(user.username!!, request.remoteAddr, userInfo!!.email!!)
}
private fun sendPasswordChangedMail(username: String, ip: String, email: String) {
val velocityContext = VelocityContext().apply {
put("appName", "氮工具")
put("appUrl", "http://localhost:5173/")
put("username", username)
put("ipAddress", ip)
}
val template = velocityEngine.getTemplate("templates/email-password-changed-cn.vm")
val stringWriter = StringWriter()
template.merge(velocityContext, stringWriter)
MailUtil.sendSimpleMail(
"您的密码已更改", stringWriter.toString(), true,
email
)
}
@EventLogRecord(EventLog.Event.LOGIN)
override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo {
val usernamePasswordAuthenticationToken =