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", "/swagger-ui.html",
"/favicon.ico", "/favicon.ico",
"/login", "/login",
"/register" "/register",
"/forget",
"/retrieve"
).anonymous() ).anonymous()
// Authentication required // Authentication required
.anyRequest().authenticated() .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.annotation.BaseController
import top.fatweb.api.entity.common.ResponseCode import top.fatweb.api.entity.common.ResponseCode
import top.fatweb.api.entity.common.ResponseResult import top.fatweb.api.entity.common.ResponseResult
import top.fatweb.api.param.permission.LoginParam import top.fatweb.api.param.permission.*
import top.fatweb.api.param.permission.RegisterParam
import top.fatweb.api.param.permission.VerifyParam
import top.fatweb.api.service.permission.IAuthenticationService import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.util.WebUtil import top.fatweb.api.util.WebUtil
import top.fatweb.api.vo.permission.LoginVo import top.fatweb.api.vo.permission.LoginVo
@@ -49,7 +47,7 @@ class AuthenticationController(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Operation(summary = "发送验证邮") @Operation(summary = "发送验证邮")
@PostMapping("/resend") @PostMapping("/resend")
fun resend(): ResponseResult<Nothing> { fun resend(): ResponseResult<Nothing> {
authenticationService.resend() authenticationService.resend()
@@ -58,12 +56,12 @@ class AuthenticationController(
} }
/** /**
* Verify * Verify email
* *
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Operation(summary = "验证") @Operation(summary = "验证邮箱")
@PostMapping("/verify") @PostMapping("/verify")
fun verify(@Valid @RequestBody verifyParam: VerifyParam): ResponseResult<Nothing> { fun verify(@Valid @RequestBody verifyParam: VerifyParam): ResponseResult<Nothing> {
authenticationService.verify(verifyParam) authenticationService.verify(verifyParam)
@@ -71,6 +69,34 @@ class AuthenticationController(
return ResponseResult.success(ResponseCode.PERMISSION_VERIFY_SUCCESS) 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 * Login
* *

View File

@@ -21,6 +21,8 @@ enum class ResponseCode(val code: Int) {
PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4), PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4),
PERMISSION_RESEND_SUCCESS(BusinessCode.PERMISSION, 5), PERMISSION_RESEND_SUCCESS(BusinessCode.PERMISSION, 5),
PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 6), PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 6),
PERMISSION_FORGET_SUCCESS(BusinessCode.PERMISSION, 7),
PERMISSION_RETRIEVE_SUCCESS(BusinessCode.PERMISSION, 8),
PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50), PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50),
PERMISSION_USERNAME_NOT_FOUND(BusinessCode.PERMISSION, 51), 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_NO_VERIFICATION_REQUIRED(BusinessCode.PERMISSION, 62),
PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 63), PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 63),
PERMISSION_ACCOUNT_NEED_INIT(BusinessCode.PERMISSION, 64), 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_SUCCESS(BusinessCode.DATABASE, 0),
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5), DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),

View File

@@ -47,7 +47,7 @@ class User() : Serializable {
var password: String? = null var password: String? = null
/** /**
* Verify * Verify email
* *
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
@@ -55,6 +55,15 @@ class User() : Serializable {
@TableField("verify") @TableField("verify")
var verify: String? = null var verify: String? = null
/**
* Forget password
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@TableField("forget")
var forget: String? = null
/** /**
* Locking * Locking
* *
@@ -242,6 +251,6 @@ class User() : Serializable {
var operations: List<Operation>? = null var operations: List<Operation>? = null
override fun toString(): String { 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) 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 -> { is NoVerificationRequiredException -> {
logger.debug(e.localizedMessage, e) logger.debug(e.localizedMessage, e)
ResponseResult.fail(ResponseCode.PERMISSION_NO_VERIFICATION_REQUIRED, e.localizedMessage, null) 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) 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 -> { is BadSqlGrammarException -> {
logger.debug(e.localizedMessage, e) 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.mapper.BaseMapper
import com.baomidou.mybatisplus.core.metadata.IPage import com.baomidou.mybatisplus.core.metadata.IPage
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Param
import top.fatweb.api.entity.permission.Group import top.fatweb.api.entity.permission.Group
/** /**
@@ -26,7 +27,11 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0 * @since 1.0.0
* @see IPage * @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 * Select group with role list by list of group IDs
@@ -37,7 +42,7 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0 * @since 1.0.0
* @see Group * @see Group
*/ */
fun selectListWithRoleByIds(groupIds: List<Long>): List<Group>? fun selectListWithRoleByIds(@Param("groupIds") groupIds: List<Long>): List<Group>?
/** /**
* Select one group by ID * Select one group by ID
@@ -48,5 +53,5 @@ interface GroupMapper : BaseMapper<Group> {
* @since 1.0.0 * @since 1.0.0
* @see Group * @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.mapper.BaseMapper
import com.baomidou.mybatisplus.core.metadata.IPage import com.baomidou.mybatisplus.core.metadata.IPage
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Param
import top.fatweb.api.entity.permission.Role import top.fatweb.api.entity.permission.Role
/** /**
@@ -26,7 +27,11 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0 * @since 1.0.0
* @see IPage * @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 * Select role with power list by list of role IDs
@@ -37,7 +42,7 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0 * @since 1.0.0
* @see Role * @see Role
*/ */
fun selectListWithPowerByIds(roleIds: List<Long>): List<Role>? fun selectListWithPowerByIds(@Param("roleIds") roleIds: List<Long>): List<Role>?
/** /**
* Select one role by ID * Select one role by ID
@@ -48,5 +53,5 @@ interface RoleMapper : BaseMapper<Role> {
* @since 1.0.0 * @since 1.0.0
* @see Role * @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 * @since 1.0.0
* @see IPage * @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 * Select user with role and information list by list of user IDs
@@ -50,7 +55,7 @@ interface UserMapper : BaseMapper<User> {
* @since 1.0.0 * @since 1.0.0
* @see User * @see User
*/ */
fun selectListWithRoleInfoByIds(userIds: List<Long>): List<User> fun selectListWithRoleInfoByIds(@Param("userIds") userIds: List<Long>): List<User>
/** /**
* Select one user by ID * Select one user by ID
@@ -61,7 +66,7 @@ interface UserMapper : BaseMapper<User> {
* @since 1.0.0 * @since 1.0.0
* @see User * @see User
*/ */
fun selectOneWithRoleInfoById(id: Long): User? fun selectOneWithRoleInfoById(@Param("id") id: Long): User?
/** /**
* Select all user with information list * Select all user with information list
@@ -81,7 +86,7 @@ interface UserMapper : BaseMapper<User> {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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 * Select user IDs list by list of group IDs
@@ -91,5 +96,5 @@ interface UserMapper : BaseMapper<User> {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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.mapper.BaseMapper
import com.baomidou.mybatisplus.core.metadata.IPage import com.baomidou.mybatisplus.core.metadata.IPage
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Param
import top.fatweb.api.entity.system.SysLog import top.fatweb.api.entity.system.SysLog
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -34,10 +35,10 @@ interface SysLogMapper : BaseMapper<SysLog> {
*/ */
fun selectPage( fun selectPage(
page: IPage<SysLog>, page: IPage<SysLog>,
logType: List<String>?, @Param("logType") logType: List<String>?,
requestMethod: List<String>?, @Param("requestMethod") requestMethod: List<String>?,
searchRequestUrl: String?, @Param("searchRequestUrl") searchRequestUrl: String?,
searchStartTime: LocalDateTime?, @Param("searchStartTime") searchStartTime: LocalDateTime?,
searchEndTime: LocalDateTime? @Param("searchEndTime") searchEndTime: LocalDateTime?
): IPage<SysLog> ): IPage<SysLog>
} }

View File

@@ -16,7 +16,7 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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") @field:Min(1, message = "Pagination page number must be a positive integer")
var currentPage: Long = 1 var currentPage: Long = 1
@@ -26,7 +26,7 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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") @field:Min(1, message = "The number of data per page must be a positive integer")
var pageSize: Long = 20 var pageSize: Long = 20
@@ -45,6 +45,6 @@ open class PageSortParam {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "排序方式", example = "desc", allowableValues = ["desc", "asc"]) @Schema(description = "排序方式", allowableValues = ["desc", "asc"], defaultValue = "desc", example = "desc")
var sortOrder: String? = null 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 * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "账户", example = "test", required = true) @Schema(description = "账户", required = true, example = "test")
@field:NotBlank(message = "Account can not be blank") @field:NotBlank(message = "Account can not be blank")
val account: String?, val account: String?,
@@ -27,7 +27,7 @@ data class LoginParam(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "密码", example = "test123456", required = true) @Schema(description = "密码", required = true)
@field:NotBlank(message = "Password can not be blank") @field:NotBlank(message = "Password can not be blank")
val password: String? val password: String?
) )

View File

@@ -19,7 +19,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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:NotBlank(message = "Username can not be blank")
@field:Pattern(regexp = "[a-zA-Z-_][0-9a-zA-Z-_]{2,38}", message = "Illegal username") @field:Pattern(regexp = "[a-zA-Z-_][0-9a-zA-Z-_]{2,38}", message = "Illegal username")
val username: String?, val username: String?,
@@ -30,7 +30,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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:NotBlank(message = "Email can not be blank")
@field:Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address") @field:Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?, val email: String?,
@@ -41,7 +41,7 @@ data class RegisterParam(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "密码", example = "test123456", required = true) @Schema(description = "密码", required = true)
@field:NotBlank(message = "Password can not be blank") @field:NotBlank(message = "Password can not be blank")
@field:Size(min = 10, max = 30) @field:Size(min = 10, max = 30)
val password: String? 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 import jakarta.validation.constraints.NotBlank
/** /**
* Verify parameters * Verify email parameters
* *
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "验证请求参数") @Schema(description = "验证邮箱请求参数")
data class VerifyParam( data class VerifyParam(
/** /**
* Code * Code
@@ -17,7 +17,7 @@ data class VerifyParam(
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @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") @field:NotBlank(message = "Code can not be blank")
val code: String?, val code: String?,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package top.fatweb.api.param.system
import com.baomidou.mybatisplus.annotation.EnumValue import com.baomidou.mybatisplus.annotation.EnumValue
import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.annotation.JsonValue
import io.swagger.v3.oas.annotations.media.Schema
/** /**
* Get active information parameters * Get active information parameters
@@ -10,6 +11,18 @@ import com.fasterxml.jackson.annotation.JsonValue
* @since 1.0.0 * @since 1.0.0
*/ */
data class ActiveInfoGetParam( 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 val scope: Scope = Scope.WEAK
) { ) {
enum class Scope(@field:EnumValue @field:JsonValue val code: String) { enum class Scope(@field:EnumValue @field:JsonValue val code: String) {

View File

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

View File

@@ -2,6 +2,7 @@ package top.fatweb.api.param.system
import com.baomidou.mybatisplus.annotation.EnumValue import com.baomidou.mybatisplus.annotation.EnumValue
import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.annotation.JsonValue
import io.swagger.v3.oas.annotations.media.Schema
/** /**
* Get online information parameters * Get online information parameters
@@ -10,6 +11,18 @@ import com.fasterxml.jackson.annotation.JsonValue
* @since 1.0.0 * @since 1.0.0
*/ */
data class OnlineInfoGetParam( 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 val scope: Scope = Scope.WEAK
) { ) {
enum class Scope(@field:EnumValue @field:JsonValue val code: String) { enum class Scope(@field:EnumValue @field:JsonValue val code: String) {

View File

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

View File

@@ -2,9 +2,7 @@ package top.fatweb.api.service.permission
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import top.fatweb.api.entity.permission.User import top.fatweb.api.entity.permission.User
import top.fatweb.api.param.permission.LoginParam import top.fatweb.api.param.permission.*
import top.fatweb.api.param.permission.RegisterParam
import top.fatweb.api.param.permission.VerifyParam
import top.fatweb.api.vo.permission.LoginVo import top.fatweb.api.vo.permission.LoginVo
import top.fatweb.api.vo.permission.RegisterVo import top.fatweb.api.vo.permission.RegisterVo
import top.fatweb.api.vo.permission.TokenVo import top.fatweb.api.vo.permission.TokenVo
@@ -33,13 +31,29 @@ interface IAuthenticationService {
fun resend() fun resend()
/** /**
* Verify * Verify email
* *
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
fun verify(verifyParam: VerifyParam) 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 * Login
* *

View File

@@ -1,5 +1,6 @@
package top.fatweb.api.service.permission.impl package top.fatweb.api.service.permission.impl
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper import com.baomidou.mybatisplus.extension.kotlin.KtUpdateWrapper
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.apache.velocity.VelocityContext 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.User
import top.fatweb.api.entity.permission.UserInfo import top.fatweb.api.entity.permission.UserInfo
import top.fatweb.api.entity.system.EventLog import top.fatweb.api.entity.system.EventLog
import top.fatweb.api.exception.AccountNeedInitException import top.fatweb.api.exception.*
import top.fatweb.api.exception.NoVerificationRequiredException import top.fatweb.api.param.permission.*
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.properties.SecurityProperties import top.fatweb.api.properties.SecurityProperties
import top.fatweb.api.service.api.v1.IAvatarService import top.fatweb.api.service.api.v1.IAvatarService
import top.fatweb.api.service.permission.IAuthenticationService import top.fatweb.api.service.permission.IAuthenticationService
@@ -71,7 +67,9 @@ class AuthenticationServiceImpl(
username = registerParam.username username = registerParam.username
password = passwordEncoder.encode(registerParam.password) password = passwordEncoder.encode(registerParam.password)
verify = 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 locking = 0
enable = 1 enable = 1
} }
@@ -83,7 +81,7 @@ class AuthenticationServiceImpl(
email = registerParam.email 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) return RegisterVo(userId = user.id)
} }
@@ -95,21 +93,23 @@ class AuthenticationServiceImpl(
user.verify ?: throw NoVerificationRequiredException() user.verify ?: throw NoVerificationRequiredException()
user.verify = 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) user.updateTime = LocalDateTime.now(ZoneOffset.UTC)
userService.updateById(user) userService.updateById(user)
WebUtil.getLoginUser()?.user?.userInfo?.email?.let { 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") } ?: 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 { val velocityContext = VelocityContext().apply {
put("appName", "氮工具") put("appName", "氮工具")
put("appUrl", "http://localhost:5173/") put("appUrl", "http://localhost:5173/")
put("username", username) put("username", username)
put("verifyUrl", verifyUrl) put("verifyUrl", "http://localhost:5173/verify?code=${code}")
} }
val template = velocityEngine.getTemplate("templates/email-verify-account-cn.vm") 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) @EventLogRecord(EventLog.Event.LOGIN)
override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo { override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo {
val usernamePasswordAuthenticationToken = val usernamePasswordAuthenticationToken =

View File

@@ -5,7 +5,8 @@ create table if not exists t_user
id bigint not null primary key, id bigint not null primary key,
username varchar(20) not null comment '用户名', username varchar(20) not null comment '用户名',
password char(70) not null comment '密码', password char(70) not null comment '密码',
verify varchar(144) null comment '验证信息', verify varchar(144) null comment '验证邮箱',
forget varchar(144) null comment '忘记密码',
locking int not null comment '锁定', locking int not null comment '锁定',
expiration datetime comment '过期时间', expiration datetime comment '过期时间',
credentials_expiration datetime comment '认证过期时间', credentials_expiration datetime comment '认证过期时间',
@@ -19,5 +20,6 @@ create table if not exists t_user
deleted bigint not null default 0, deleted bigint not null default 0,
version int not null default 0, version int not null default 0,
constraint t_user_unique_username unique (username, deleted), constraint t_user_unique_username unique (username, deleted),
constraint t_user_unique_verify unique (verify, deleted) constraint t_user_unique_verify unique (verify, deleted),
constraint t_user_unique_forget unique (forget, deleted)
) comment '用户表'; ) comment '用户表';

View File

@@ -0,0 +1,78 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>您的密码已更改</title>
<style>
* {
margin: 0;
padding: 0;
color: #4D4D4D;
}
a {
color: #4E47BB;
}
body {
display: flex;
background-color: #F1F2F7;
justify-content: center;
flex-wrap: wrap;
}
.view {
display: flex;
width: 800px;
max-width: 100vw;
align-content: center;
}
.card {
margin: 20px;
padding: 20px;
border-radius: 12px;
flex: 1;
background-color: white;
}
.title {
margin-bottom: 10px;
text-align: center;
font-weight: bolder;
font-size: 1.6em;
color: #4E47BB;
}
.retrieve-button a {
padding: 20px 30px;
color: white;
text-decoration: none;
font-size: 1.2em;
background-color: #4E47BB;
border-radius: 10px;
}
.not-reply {
margin-top: 20px;
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="view">
<div class="card">
<div class="title">密&nbsp;码&nbsp;已&nbsp;更&nbsp;改</div>
<div><strong>${username}</strong>,您好:</div>
<div style="text-indent: 2em">您在 <a target="_blank" href=${appUrl}>${appName}(${appUrl})</a> 的密码已更改,操作
IP 地址为 [${ipAddress}]。如果不是您自己的操作,请尽快检查您的账号!
</div>
<div class="not-reply">此邮件由系统自动发送,请勿回复!</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,90 @@
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>找回您的密码</title>
<style>
* {
margin: 0;
padding: 0;
color: #4D4D4D;
}
a {
color: #4E47BB;
}
body {
display: flex;
background-color: #F1F2F7;
justify-content: center;
flex-wrap: wrap;
}
.view {
display: flex;
width: 800px;
max-width: 100vw;
align-content: center;
}
.card {
margin: 20px;
padding: 20px;
border-radius: 12px;
flex: 1;
background-color: white;
}
.title {
margin-bottom: 10px;
text-align: center;
font-weight: bolder;
font-size: 1.6em;
color: #4E47BB;
}
.retrieve-button {
display: flex;
margin: 20px;
padding-bottom: 60px;
justify-content: center;
}
.retrieve-button a {
padding: 20px 30px;
color: white;
text-decoration: none;
font-size: 1.2em;
background-color: #4E47BB;
border-radius: 10px;
}
.not-reply {
margin-top: 20px;
text-align: center;
font-weight: bold;
}
</style>
</head>
<body>
<div class="view">
<div class="card">
<div class="title">找&nbsp;回&nbsp;密&nbsp;码</div>
<div><strong>${username}</strong>,您好:</div>
<div style="text-indent: 2em">您正在找回 <a target="_blank" href=${appUrl}>${appName}(${appUrl})</a> 的密码,操作
IP 地址为 [${ipAddress}],如果是您自己的操作,请在 <u><i>两小时内</i></u> 点击下面的按钮找回您的密码:
</div>
<div class="retrieve-button"><a target="_blank" href=${retrieveUrl}>找回密码</a></div>
<div>如果以上按钮无法点击,请复制此链接到浏览器地址栏中访问:<a target="_blank"
href=${retrieveUrl}>${retrieveUrl}</a></div>
<div>如果并非本人操作,请忽略该邮件</div>
<div class="not-reply">此邮件由系统自动发送,请勿回复!</div>
</div>
</div>
</body>
</html>

View File

@@ -54,6 +54,7 @@
justify-content: center; justify-content: center;
} }
.verify-button a { .verify-button a {
padding: 20px 30px; padding: 20px 30px;
color: white; color: white;
@@ -75,9 +76,12 @@
<div class="card"> <div class="card">
<div class="title">账&nbsp;号&nbsp;激&nbsp;活</div> <div class="title">账&nbsp;号&nbsp;激&nbsp;活</div>
<div><strong>${username}</strong>,您好:</div> <div><strong>${username}</strong>,您好:</div>
<div style="text-indent: 2em">感谢注册 <a target="_blank" href=${appUrl}>${appName}(${appUrl})</a>,在继续使用之前,我们需要确定您的电子邮箱地址的有效性,请在 <u><i>两小时内</i></u> 点击下面的按钮帮助我们验证:</div> <div style="text-indent: 2em">感谢注册 <a target="_blank" href=${appUrl}>${appName}(${appUrl})</a>,在继续使用之前,我们需要确定您的电子邮箱地址的有效性,请在
<u><i>两小时内</i></u> 点击下面的按钮帮助我们验证:
</div>
<div class="verify-button"><a target="_blank" href=${verifyUrl}>验证邮箱</a></div> <div class="verify-button"><a target="_blank" href=${verifyUrl}>验证邮箱</a></div>
<div>如果以上按钮无法点击,请复制此链接到浏览器地址栏中访问:<a target="_blank" href=${verifyUrl}>${verifyUrl}</a></div> <div>如果以上按钮无法点击,请复制此链接到浏览器地址栏中访问:<a target="_blank" href=${verifyUrl}>${verifyUrl}</a>
</div>
<div class="not-reply">此邮件由系统自动发送,请勿回复!</div> <div class="not-reply">此邮件由系统自动发送,请勿回复!</div>
</div> </div>
</div> </div>