Support login with email

This commit is contained in:
2023-12-20 14:55:30 +08:00
parent 60353906ad
commit e7c8311b83
13 changed files with 34 additions and 43 deletions

View File

@@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestBody
import top.fatweb.api.annotation.BaseController import top.fatweb.api.annotation.BaseController
import top.fatweb.api.converter.permission.UserConverter
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.LoginParam
@@ -46,7 +45,7 @@ class AuthenticationController(
ResponseResult.success( ResponseResult.success(
ResponseCode.PERMISSION_LOGIN_SUCCESS, ResponseCode.PERMISSION_LOGIN_SUCCESS,
"Login success", "Login success",
authenticationService.login(request, UserConverter.loginParamToUser(loginParam)) authenticationService.login(request, loginParam)
) )
/** /**

View File

@@ -5,7 +5,6 @@ import top.fatweb.api.entity.permission.Group
import top.fatweb.api.entity.permission.Role import top.fatweb.api.entity.permission.Role
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.param.permission.LoginParam
import top.fatweb.api.param.permission.user.UserAddParam import top.fatweb.api.param.permission.user.UserAddParam
import top.fatweb.api.param.permission.user.UserUpdateParam import top.fatweb.api.param.permission.user.UserUpdateParam
import top.fatweb.api.vo.PageVo import top.fatweb.api.vo.PageVo
@@ -22,21 +21,6 @@ import top.fatweb.avatargenerator.GitHubAvatar
* @since 1.0.0 * @since 1.0.0
*/ */
object UserConverter { object UserConverter {
/**
* Convert LoginParam object into User object
*
* @param loginParam LoginParam object
* @return User object
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
* @see LoginParam
* @see User
*/
fun loginParamToUser(loginParam: LoginParam) = User().apply {
username = loginParam.username
password = loginParam.password
}
/** /**
* Convert User object into UserWithPowerInfoVo object * Convert User object into UserWithPowerInfoVo object
* *

View File

@@ -17,15 +17,15 @@ import top.fatweb.api.entity.permission.User
@Mapper @Mapper
interface UserMapper : BaseMapper<User> { interface UserMapper : BaseMapper<User> {
/** /**
* Select one user with power and information by username * Select one user with power and information by username or email
* *
* @param username Username * @param account Username or email
* @return User object with power and information * @return User object with power and information
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
* @see User * @see User
*/ */
fun selectOneWithPowerInfoByUsername(@Param("username") username: String): User? fun selectOneWithPowerInfoByAccount(@Param("account") account: String): User?
/** /**
* Select user in page * Select user in page

View File

@@ -12,14 +12,14 @@ import jakarta.validation.constraints.NotBlank
@Schema(description = "登录请求参数") @Schema(description = "登录请求参数")
data class LoginParam( data class LoginParam(
/** /**
* Username * Account
* *
* @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 = "账户", example = "test", required = true)
@field:NotBlank(message = "Username can not be blank") @field:NotBlank(message = "Account can not be blank")
val username: String? = null, val account: String? = null,
/** /**
* Password * Password

View File

@@ -2,6 +2,7 @@ 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.NotBlank
import jakarta.validation.constraints.Pattern
import java.time.LocalDateTime import java.time.LocalDateTime
/** /**
@@ -94,6 +95,7 @@ data class UserAddParam(
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "邮箱") @Schema(description = "邮箱")
@Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?, val email: String?,
/** /**

View File

@@ -2,6 +2,7 @@ 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.NotNull import jakarta.validation.constraints.NotNull
import jakarta.validation.constraints.Pattern
import java.time.LocalDateTime import java.time.LocalDateTime
/** /**
@@ -94,6 +95,7 @@ data class UserUpdateParam(
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "邮箱") @Schema(description = "邮箱")
@Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
val email: String?, val email: String?,
/** /**

View File

@@ -2,6 +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.vo.permission.LoginVo import top.fatweb.api.vo.permission.LoginVo
import top.fatweb.api.vo.permission.TokenVo import top.fatweb.api.vo.permission.TokenVo
@@ -16,7 +17,7 @@ interface IAuthenticationService {
* Login * Login
* *
* @param request * @param request
* @param user User object * @param loginParam Login parameters
* @return LoginVo object * @return LoginVo object
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
@@ -24,7 +25,7 @@ interface IAuthenticationService {
* @see User * @see User
* @see LoginVo * @see LoginVo
*/ */
fun login(request: HttpServletRequest, user: User): LoginVo fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo
/** /**
* Logout * Logout

View File

@@ -19,15 +19,15 @@ import top.fatweb.api.vo.permission.UserWithRoleInfoVo
*/ */
interface IUserService : IService<User> { interface IUserService : IService<User> {
/** /**
* Get user with power by username * Get user with power by username or email
* *
* @param username Username * @param account Username or email
* @return User object * @return User object
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
* @see User * @see User
*/ */
fun getUserWithPowerByUsername(username: String): User? fun getUserWithPowerByAccount(account: String): User?
/** /**
* Get user information * Get user information

View File

@@ -12,6 +12,7 @@ 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.system.EventLog import top.fatweb.api.entity.system.EventLog
import top.fatweb.api.exception.TokenHasExpiredException import top.fatweb.api.exception.TokenHasExpiredException
import top.fatweb.api.param.permission.LoginParam
import top.fatweb.api.properties.SecurityProperties import top.fatweb.api.properties.SecurityProperties
import top.fatweb.api.service.permission.IAuthenticationService import top.fatweb.api.service.permission.IAuthenticationService
import top.fatweb.api.service.permission.IUserService import top.fatweb.api.service.permission.IUserService
@@ -42,8 +43,9 @@ class AuthenticationServiceImpl(
private val logger: Logger = LoggerFactory.getLogger(this::class.java) private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@EventLogRecord(EventLog.Event.LOGIN) @EventLogRecord(EventLog.Event.LOGIN)
override fun login(request: HttpServletRequest, user: User): LoginVo { override fun login(request: HttpServletRequest, loginParam: LoginParam): LoginVo {
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(user.username, user.password) val usernamePasswordAuthenticationToken =
UsernamePasswordAuthenticationToken(loginParam.account, loginParam.password)
val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken) val authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken)
authentication ?: let { authentication ?: let {
throw RuntimeException("Login failed") throw RuntimeException("Login failed")
@@ -52,13 +54,13 @@ class AuthenticationServiceImpl(
val loginUser = authentication.principal as LoginUser val loginUser = authentication.principal as LoginUser
loginUser.user.password = "" loginUser.user.password = ""
logger.info("用户登录 [用户名: '{}', IP: '{}']", user.username, request.remoteAddr) logger.info("用户登录 [用户名: '{}', IP: '{}']", loginUser.username, request.remoteAddr)
userService.update(User().apply { userService.update(User().apply {
currentLoginIp = request.remoteAddr currentLoginIp = request.remoteAddr
currentLoginTime = LocalDateTime.now(ZoneOffset.UTC) currentLoginTime = LocalDateTime.now(ZoneOffset.UTC)
lastLoginIp = loginUser.user.currentLoginIp lastLoginIp = loginUser.user.currentLoginIp
lastLoginTime = loginUser.user.currentLoginTime lastLoginTime = loginUser.user.currentLoginTime
}, KtUpdateWrapper(User()).eq(User::username, user.username)) }, KtUpdateWrapper(User()).eq(User::username, loginUser.username))
val userId = loginUser.user.id.toString() val userId = loginUser.user.id.toString()
val jwt = JwtUtil.createJwt(userId) val jwt = JwtUtil.createJwt(userId)

View File

@@ -16,8 +16,8 @@ import top.fatweb.api.service.permission.IUserService
*/ */
@Service @Service
class UserDetailsServiceImpl(val userService: IUserService) : UserDetailsService { class UserDetailsServiceImpl(val userService: IUserService) : UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails { override fun loadUserByUsername(account: String): UserDetails {
val user = userService.getUserWithPowerByUsername(username) val user = userService.getUserWithPowerByAccount(account)
user ?: let { throw Exception("Username not found") } user ?: let { throw Exception("Username not found") }
return LoginUser(user) return LoginUser(user)

View File

@@ -61,8 +61,8 @@ class UserServiceImpl(
private val userRoleService: IUserRoleService, private val userRoleService: IUserRoleService,
private val userGroupService: IUserGroupService private val userGroupService: IUserGroupService
) : ServiceImpl<UserMapper, User>(), IUserService { ) : ServiceImpl<UserMapper, User>(), IUserService {
override fun getUserWithPowerByUsername(username: String): User? { override fun getUserWithPowerByAccount(account: String): User? {
val user = baseMapper.selectOneWithPowerInfoByUsername(username) val user = baseMapper.selectOneWithPowerInfoByAccount(account)
user ?: let { return null } user ?: let { return null }
if (user.id == 0L) { if (user.id == 0L) {
@@ -76,7 +76,7 @@ class UserServiceImpl(
} }
override fun getInfo() = WebUtil.getLoginUsername() override fun getInfo() = WebUtil.getLoginUsername()
?.let { username -> getUserWithPowerByUsername(username)?.let { UserConverter.userToUserWithPowerInfoVo(it) } } ?.let { username -> getUserWithPowerByAccount(username)?.let { UserConverter.userToUserWithPowerInfoVo(it) } }
override fun getPage(userGetParam: UserGetParam?): PageVo<UserWithRoleInfoVo> { override fun getPage(userGetParam: UserGetParam?): PageVo<UserWithRoleInfoVo> {
val userIdsPage = Page<Long>(userGetParam?.currentPage ?: 1, userGetParam?.pageSize ?: 20) val userIdsPage = Page<Long>(userGetParam?.currentPage ?: 1, userGetParam?.pageSize ?: 20)

View File

@@ -6,10 +6,11 @@ create table if not exists t_user_info
user_id bigint not null comment '用户ID', user_id bigint not null comment '用户ID',
nickname varchar(50) null comment '昵称', nickname varchar(50) null comment '昵称',
avatar text null comment '头像', avatar text null comment '头像',
email varchar(100) null comment '邮箱', email varchar(100) not null comment '邮箱',
create_time datetime not null default (utc_timestamp()) comment '创建时间', create_time datetime not null default (utc_timestamp()) comment '创建时间',
update_time datetime not null default (utc_timestamp()) comment '修改时间', update_time datetime not null default (utc_timestamp()) comment '修改时间',
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_info_unique unique (user_id, deleted) constraint t_user_info_unique_user_id unique (user_id, deleted),
constraint t_user_info_unique_email unique (email, deleted)
) comment '用户资料表'; ) comment '用户资料表';

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="top.fatweb.api.mapper.permission.UserMapper"> <mapper namespace="top.fatweb.api.mapper.permission.UserMapper">
<select id="selectOneWithPowerInfoByUsername" resultMap="userWithPowerInfoMap"> <select id="selectOneWithPowerInfoByAccount" resultMap="userWithPowerInfoMap">
select t_user.id as user_id, select t_user.id as user_id,
t_user.username as user_username, t_user.username as user_username,
t_user.password as user_password, t_user.password as user_password,
@@ -55,7 +55,7 @@
left join t_func as tf on tp.id = tf.id left join t_func as tf on tp.id = tf.id
left join t_operation as t on tp.id = t.id left join t_operation as t on tp.id = t.id
where t_user.deleted = 0 where t_user.deleted = 0
and t_user.username = #{username}; and (tui.email = #{account} or t_user.username = #{account});
</select> </select>
<select id="selectPage" resultType="long"> <select id="selectPage" resultType="long">