Add account register and verify api
This commit is contained in:
@@ -2,6 +2,7 @@ package top.fatweb.api.config
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
|
import com.baomidou.mybatisplus.extension.kotlin.KtQueryWrapper
|
||||||
import jakarta.annotation.PostConstruct
|
import jakarta.annotation.PostConstruct
|
||||||
|
import org.apache.velocity.app.VelocityEngine
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.context.annotation.DependsOn
|
import org.springframework.context.annotation.DependsOn
|
||||||
@@ -29,7 +30,7 @@ import top.fatweb.avatargenerator.GitHubAvatar
|
|||||||
class InitConfig(
|
class InitConfig(
|
||||||
private val userService: IUserService,
|
private val userService: IUserService,
|
||||||
private val userInfoService: IUserInfoService,
|
private val userInfoService: IUserInfoService,
|
||||||
private val passwordEncoder: PasswordEncoder,
|
private val passwordEncoder: PasswordEncoder
|
||||||
) {
|
) {
|
||||||
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
|
||||||
|
|||||||
@@ -22,5 +22,4 @@ class MybatisPlusConfig {
|
|||||||
|
|
||||||
return mybatisPlusInterceptor
|
return mybatisPlusInterceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -75,6 +75,7 @@ class SecurityConfig(
|
|||||||
"/swagger-ui.html",
|
"/swagger-ui.html",
|
||||||
"/favicon.ico",
|
"/favicon.ico",
|
||||||
"/login",
|
"/login",
|
||||||
|
"/register"
|
||||||
).anonymous()
|
).anonymous()
|
||||||
// Authentication required
|
// Authentication required
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package top.fatweb.api.config
|
||||||
|
|
||||||
|
import org.apache.velocity.app.VelocityEngine
|
||||||
|
import org.apache.velocity.runtime.RuntimeConstants
|
||||||
|
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
|
||||||
|
import org.springframework.context.annotation.Bean
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class VelocityEngineConfig {
|
||||||
|
@Bean
|
||||||
|
fun velocityEngine() = VelocityEngine().apply {
|
||||||
|
setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath")
|
||||||
|
setProperty("classpath.resource.loader.class", ClasspathResourceLoader::class.java.name)
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,20 @@ class AuthenticationController(
|
|||||||
return ResponseResult.success(ResponseCode.PERMISSION_REGISTER_SUCCESS)
|
return ResponseResult.success(ResponseCode.PERMISSION_REGISTER_SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send verify email
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Operation(summary = "发送验证邮箱")
|
||||||
|
@PostMapping("/resend")
|
||||||
|
fun resend(): ResponseResult<Nothing> {
|
||||||
|
authenticationService.resend()
|
||||||
|
|
||||||
|
return ResponseResult.success(ResponseCode.PERMISSION_RESEND_SUCCESS)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify
|
* Verify
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ object UserConverter {
|
|||||||
fun userToUserWithPowerInfoVo(user: User) = UserWithPowerInfoVo(
|
fun userToUserWithPowerInfoVo(user: User) = UserWithPowerInfoVo(
|
||||||
id = user.id,
|
id = user.id,
|
||||||
username = user.username,
|
username = user.username,
|
||||||
|
verified = user.verify.isNullOrBlank(),
|
||||||
locking = user.locking?.let { it == 1 },
|
locking = user.locking?.let { it == 1 },
|
||||||
expiration = user.expiration,
|
expiration = user.expiration,
|
||||||
credentialsExpiration = user.credentialsExpiration,
|
credentialsExpiration = user.credentialsExpiration,
|
||||||
@@ -109,6 +110,7 @@ object UserConverter {
|
|||||||
fun userToUserWithInfoVo(user: User) = UserWithInfoVo(
|
fun userToUserWithInfoVo(user: User) = UserWithInfoVo(
|
||||||
id = user.id,
|
id = user.id,
|
||||||
username = user.username,
|
username = user.username,
|
||||||
|
verified = user.verify.isNullOrBlank(),
|
||||||
locking = user.locking?.let { it == 1 },
|
locking = user.locking?.let { it == 1 },
|
||||||
expiration = user.expiration,
|
expiration = user.expiration,
|
||||||
credentialsExpiration = user.credentialsExpiration,
|
credentialsExpiration = user.credentialsExpiration,
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ enum class ResponseCode(val code: Int) {
|
|||||||
PERMISSION_LOGOUT_SUCCESS(BusinessCode.PERMISSION, 2),
|
PERMISSION_LOGOUT_SUCCESS(BusinessCode.PERMISSION, 2),
|
||||||
PERMISSION_TOKEN_RENEW_SUCCESS(BusinessCode.PERMISSION, 3),
|
PERMISSION_TOKEN_RENEW_SUCCESS(BusinessCode.PERMISSION, 3),
|
||||||
PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4),
|
PERMISSION_REGISTER_SUCCESS(BusinessCode.PERMISSION, 4),
|
||||||
PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 5),
|
PERMISSION_RESEND_SUCCESS(BusinessCode.PERMISSION, 5),
|
||||||
|
PERMISSION_VERIFY_SUCCESS(BusinessCode.PERMISSION, 6),
|
||||||
|
|
||||||
PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50),
|
PERMISSION_UNAUTHORIZED(BusinessCode.PERMISSION, 50),
|
||||||
PERMISSION_USERNAME_NOT_FOUND(BusinessCode.PERMISSION, 51),
|
PERMISSION_USERNAME_NOT_FOUND(BusinessCode.PERMISSION, 51),
|
||||||
@@ -33,6 +34,9 @@ enum class ResponseCode(val code: Int) {
|
|||||||
PERMISSION_LOGOUT_FAILED(BusinessCode.PERMISSION, 59),
|
PERMISSION_LOGOUT_FAILED(BusinessCode.PERMISSION, 59),
|
||||||
PERMISSION_TOKEN_ILLEGAL(BusinessCode.PERMISSION, 60),
|
PERMISSION_TOKEN_ILLEGAL(BusinessCode.PERMISSION, 60),
|
||||||
PERMISSION_TOKEN_HAS_EXPIRED(BusinessCode.PERMISSION, 61),
|
PERMISSION_TOKEN_HAS_EXPIRED(BusinessCode.PERMISSION, 61),
|
||||||
|
PERMISSION_NO_VERIFICATION_REQUIRED(BusinessCode.PERMISSION, 62),
|
||||||
|
PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED(BusinessCode.PERMISSION, 63),
|
||||||
|
PERMISSION_ACCOUNT_NEED_INIT(BusinessCode.PERMISSION, 64),
|
||||||
|
|
||||||
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
|
DATABASE_SELECT_SUCCESS(BusinessCode.DATABASE, 0),
|
||||||
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),
|
DATABASE_SELECT_FAILED(BusinessCode.DATABASE, 5),
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package top.fatweb.api.exception
|
||||||
|
|
||||||
|
class AccountNeedInitException : RuntimeException("Account need initialize")
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package top.fatweb.api.exception
|
||||||
|
|
||||||
|
class NoEmailConfigException(
|
||||||
|
vararg configs: String
|
||||||
|
) : RuntimeException("Email settings not configured: ${configs.joinToString(", ")}")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package top.fatweb.api.exception
|
||||||
|
|
||||||
|
class NoVerificationRequiredException : RuntimeException("No verification required")
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package top.fatweb.api.exception
|
||||||
|
|
||||||
|
class VerificationCodeErrorOrExpiredException : RuntimeException("Verification code is error or has expired")
|
||||||
@@ -16,8 +16,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler
|
|||||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||||
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.exception.NoRecordFoundException
|
import top.fatweb.api.exception.*
|
||||||
import top.fatweb.api.exception.TokenHasExpiredException
|
|
||||||
import top.fatweb.avatargenerator.AvatarException
|
import top.fatweb.avatargenerator.AvatarException
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,6 +121,22 @@ class ExceptionHandler {
|
|||||||
ResponseResult.fail(ResponseCode.PERMISSION_ACCESS_DENIED, "Access Denied", null)
|
ResponseResult.fail(ResponseCode.PERMISSION_ACCESS_DENIED, "Access Denied", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is NoVerificationRequiredException -> {
|
||||||
|
logger.debug(e.localizedMessage, e)
|
||||||
|
ResponseResult.fail(ResponseCode.PERMISSION_NO_VERIFICATION_REQUIRED, e.localizedMessage, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
is VerificationCodeErrorOrExpiredException -> {
|
||||||
|
logger.debug(e.localizedMessage, e)
|
||||||
|
ResponseResult.fail(ResponseCode.PERMISSION_VERIFY_CODE_ERROR_OR_EXPIRED, e.localizedMessage, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
is AccountNeedInitException -> {
|
||||||
|
logger.debug(e.localizedMessage, e)
|
||||||
|
ResponseResult.fail(ResponseCode.PERMISSION_ACCOUNT_NEED_INIT, e.localizedMessage, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
is BadSqlGrammarException -> {
|
is BadSqlGrammarException -> {
|
||||||
logger.debug(e.localizedMessage, e)
|
logger.debug(e.localizedMessage, e)
|
||||||
ResponseResult.fail(ResponseCode.DATABASE_EXECUTE_ERROR, "Incorrect SQL syntax", null)
|
ResponseResult.fail(ResponseCode.DATABASE_EXECUTE_ERROR, "Incorrect SQL syntax", null)
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package top.fatweb.api.param.permission
|
package top.fatweb.api.param.permission
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema
|
import io.swagger.v3.oas.annotations.media.Schema
|
||||||
import jakarta.validation.constraints.Email
|
|
||||||
import jakarta.validation.constraints.NotBlank
|
import jakarta.validation.constraints.NotBlank
|
||||||
|
import jakarta.validation.constraints.Pattern
|
||||||
|
import jakarta.validation.constraints.Size
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register parameters
|
* Register parameters
|
||||||
@@ -12,6 +13,17 @@ import jakarta.validation.constraints.NotBlank
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "注册请求参数")
|
@Schema(description = "注册请求参数")
|
||||||
data class RegisterParam(
|
data class RegisterParam(
|
||||||
|
/**
|
||||||
|
* Username
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Schema(description = "用户名", example = "abc", required = true)
|
||||||
|
@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?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email
|
* Email
|
||||||
*
|
*
|
||||||
@@ -19,7 +31,8 @@ data class RegisterParam(
|
|||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
@Schema(description = "邮箱", example = "guest@fatweb.top", required = true)
|
@Schema(description = "邮箱", example = "guest@fatweb.top", required = true)
|
||||||
@field:Email(message = "Illegal email address")
|
@field:NotBlank(message = "Email can not be blank")
|
||||||
|
@field:Pattern(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*\$", message = "Illegal email address")
|
||||||
val email: String?,
|
val email: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -30,5 +43,6 @@ data class RegisterParam(
|
|||||||
*/
|
*/
|
||||||
@Schema(description = "密码", example = "test123456", required = true)
|
@Schema(description = "密码", example = "test123456", required = true)
|
||||||
@field:NotBlank(message = "Password can not be blank")
|
@field:NotBlank(message = "Password can not be blank")
|
||||||
|
@field:Size(min = 10, max = 30)
|
||||||
val password: String?
|
val password: String?
|
||||||
)
|
)
|
||||||
@@ -21,15 +21,6 @@ data class VerifyParam(
|
|||||||
@field:NotBlank(message = "Code can not be blank")
|
@field:NotBlank(message = "Code can not be blank")
|
||||||
val code: String?,
|
val code: String?,
|
||||||
|
|
||||||
/**
|
|
||||||
* Username
|
|
||||||
*
|
|
||||||
* @author FatttSnake, fatttsnake@gmail.com
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
@Schema(description = "用户名", example = "adb")
|
|
||||||
val username: String?,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nickname
|
* Nickname
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ interface IAuthenticationService {
|
|||||||
*/
|
*/
|
||||||
fun register(registerParam: RegisterParam)
|
fun register(registerParam: RegisterParam)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send verify email
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
fun resend()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify
|
* Verify
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package top.fatweb.api.service.permission.impl
|
|||||||
|
|
||||||
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.app.VelocityEngine
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.security.access.AccessDeniedException
|
||||||
import org.springframework.security.authentication.AuthenticationManager
|
import org.springframework.security.authentication.AuthenticationManager
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
@@ -14,7 +17,10 @@ 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.NoVerificationRequiredException
|
||||||
import top.fatweb.api.exception.TokenHasExpiredException
|
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.LoginParam
|
||||||
import top.fatweb.api.param.permission.RegisterParam
|
import top.fatweb.api.param.permission.RegisterParam
|
||||||
import top.fatweb.api.param.permission.VerifyParam
|
import top.fatweb.api.param.permission.VerifyParam
|
||||||
@@ -24,10 +30,13 @@ import top.fatweb.api.service.permission.IAuthenticationService
|
|||||||
import top.fatweb.api.service.permission.IUserInfoService
|
import top.fatweb.api.service.permission.IUserInfoService
|
||||||
import top.fatweb.api.service.permission.IUserService
|
import top.fatweb.api.service.permission.IUserService
|
||||||
import top.fatweb.api.util.JwtUtil
|
import top.fatweb.api.util.JwtUtil
|
||||||
|
import top.fatweb.api.util.MailUtil
|
||||||
import top.fatweb.api.util.RedisUtil
|
import top.fatweb.api.util.RedisUtil
|
||||||
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
|
||||||
import top.fatweb.api.vo.permission.TokenVo
|
import top.fatweb.api.vo.permission.TokenVo
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.time.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -44,6 +53,7 @@ import java.util.*
|
|||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
class AuthenticationServiceImpl(
|
class AuthenticationServiceImpl(
|
||||||
|
private val velocityEngine: VelocityEngine,
|
||||||
private val authenticationManager: AuthenticationManager,
|
private val authenticationManager: AuthenticationManager,
|
||||||
private val passwordEncoder: PasswordEncoder,
|
private val passwordEncoder: PasswordEncoder,
|
||||||
private val redisUtil: RedisUtil,
|
private val redisUtil: RedisUtil,
|
||||||
@@ -56,8 +66,10 @@ class AuthenticationServiceImpl(
|
|||||||
@Transactional
|
@Transactional
|
||||||
override fun register(registerParam: RegisterParam) {
|
override fun register(registerParam: RegisterParam) {
|
||||||
val user = User().apply {
|
val user = User().apply {
|
||||||
username = "\$UNNAMED_${UUID.randomUUID()}"
|
username = registerParam.username
|
||||||
password = passwordEncoder.encode(registerParam.password)
|
password = passwordEncoder.encode(registerParam.password)
|
||||||
|
verify =
|
||||||
|
"${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${UUID.randomUUID()}"
|
||||||
locking = 0
|
locking = 0
|
||||||
enable = 1
|
enable = 1
|
||||||
}
|
}
|
||||||
@@ -67,9 +79,66 @@ class AuthenticationServiceImpl(
|
|||||||
avatar = avatarService.randomBase64(null).base64
|
avatar = avatarService.randomBase64(null).base64
|
||||||
email = registerParam.email
|
email = registerParam.email
|
||||||
})
|
})
|
||||||
|
|
||||||
|
sendVerifyMail(user.username!!, "http://localhost:5173/verify?code=${user.verify!!}", registerParam.email!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
override fun resend() {
|
||||||
|
val user = userService.getById(WebUtil.getLoginUserId()) ?: throw AccessDeniedException("Access Denied")
|
||||||
|
|
||||||
|
user.verify ?: throw NoVerificationRequiredException()
|
||||||
|
|
||||||
|
user.verify =
|
||||||
|
"${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${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)
|
||||||
|
} ?: throw AccessDeniedException("Access Denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendVerifyMail(username: String, verifyUrl: String, email: String) {
|
||||||
|
val velocityContext = VelocityContext().apply {
|
||||||
|
put("appName", "氮工具")
|
||||||
|
put("appUrl", "http://localhost:5173/")
|
||||||
|
put("username", username)
|
||||||
|
put("verifyUrl", verifyUrl)
|
||||||
|
}
|
||||||
|
val template = velocityEngine.getTemplate("templates/email-verify-account-cn.vm")
|
||||||
|
|
||||||
|
val stringWriter = StringWriter()
|
||||||
|
template.merge(velocityContext, stringWriter)
|
||||||
|
|
||||||
|
MailUtil.sendSimpleMail(
|
||||||
|
"激活您的账号", stringWriter.toString(), true,
|
||||||
|
email
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
override fun verify(verifyParam: VerifyParam) {
|
override fun verify(verifyParam: VerifyParam) {
|
||||||
|
val user = userService.getById(WebUtil.getLoginUserId()) ?: throw AccessDeniedException("Access Denied")
|
||||||
|
user.verify ?: throw NoVerificationRequiredException()
|
||||||
|
if (LocalDateTime.ofInstant(Instant.ofEpochMilli(user.verify!!.split("-").first().toLong()), ZoneOffset.UTC)
|
||||||
|
.isBefore(LocalDateTime.now(ZoneOffset.UTC).minusHours(2)) || user.verify != verifyParam.code
|
||||||
|
) {
|
||||||
|
throw VerificationCodeErrorOrExpiredException()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verifyParam.nickname.isNullOrBlank() || verifyParam.avatar.isNullOrBlank()) {
|
||||||
|
throw AccountNeedInitException()
|
||||||
|
}
|
||||||
|
|
||||||
|
userService.update(
|
||||||
|
KtUpdateWrapper(User()).eq(User::id, user.id).set(User::verify, null)
|
||||||
|
.set(User::updateTime, LocalDateTime.now(ZoneOffset.UTC))
|
||||||
|
)
|
||||||
|
userInfoService.update(
|
||||||
|
KtUpdateWrapper(UserInfo()).eq(UserInfo::userId, user.id).set(UserInfo::nickname, verifyParam.nickname)
|
||||||
|
.set(UserInfo::avatar, verifyParam.avatar)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventLogRecord(EventLog.Event.LOGIN)
|
@EventLogRecord(EventLog.Event.LOGIN)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class SettingsServiceImpl : ISettingsService {
|
|||||||
MailUtil.sendSimpleMail(
|
MailUtil.sendSimpleMail(
|
||||||
"${ServerProperties.appName} Test Message",
|
"${ServerProperties.appName} Test Message",
|
||||||
"This is a test email sent when testing the system email sending service.",
|
"This is a test email sent when testing the system email sending service.",
|
||||||
|
false,
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package top.fatweb.api.util
|
package top.fatweb.api.util
|
||||||
|
|
||||||
import org.springframework.mail.MailSender
|
|
||||||
import org.springframework.mail.SimpleMailMessage
|
|
||||||
import org.springframework.mail.javamail.JavaMailSenderImpl
|
import org.springframework.mail.javamail.JavaMailSenderImpl
|
||||||
|
import org.springframework.mail.javamail.MimeMessageHelper
|
||||||
|
import top.fatweb.api.exception.NoEmailConfigException
|
||||||
import top.fatweb.api.settings.MailSecurityType
|
import top.fatweb.api.settings.MailSecurityType
|
||||||
import top.fatweb.api.settings.MailSettings
|
import top.fatweb.api.settings.MailSettings
|
||||||
import top.fatweb.api.settings.SettingsOperator
|
import top.fatweb.api.settings.SettingsOperator
|
||||||
@@ -33,15 +33,19 @@ object MailUtil {
|
|||||||
mailSender.javaMailProperties = properties
|
mailSender.javaMailProperties = properties
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSender(): MailSender = mailSender
|
fun sendSimpleMail(subject: String, text: String, html: Boolean = false, vararg to: String) {
|
||||||
|
val fromName = SettingsOperator.getMailValue(MailSettings::fromName) ?: throw NoEmailConfigException("fromName")
|
||||||
|
val from = SettingsOperator.getMailValue(MailSettings::from) ?: throw NoEmailConfigException("from")
|
||||||
|
|
||||||
fun sendSimpleMail(subject: String, text: String, vararg to: String) {
|
val mimeMessage = mailSender.createMimeMessage()
|
||||||
mailSender.send(SimpleMailMessage().apply {
|
val messageHelper = MimeMessageHelper(mimeMessage, true)
|
||||||
|
messageHelper.apply {
|
||||||
setSubject(subject)
|
setSubject(subject)
|
||||||
from = "${SettingsOperator.getMailValue(MailSettings::fromName)}<${SettingsOperator.getMailValue(MailSettings::from)}>"
|
setFrom(from, fromName)
|
||||||
sentDate = Date()
|
setSentDate(Date())
|
||||||
setTo(*to)
|
setTo(to)
|
||||||
setText(text)
|
setText(text, html)
|
||||||
})
|
}
|
||||||
|
mailSender.send(mimeMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,6 +32,15 @@ data class UserWithInfoVo(
|
|||||||
@Schema(description = "用户名", example = "User")
|
@Schema(description = "用户名", example = "User")
|
||||||
val username: String?,
|
val username: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verified
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Schema(description = "已验证", example = "true")
|
||||||
|
val verified: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locking
|
* Locking
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ data class UserWithPowerInfoVo(
|
|||||||
@Schema(description = "用户名", example = "User")
|
@Schema(description = "用户名", example = "User")
|
||||||
val username: String?,
|
val username: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verified
|
||||||
|
*
|
||||||
|
* @author FatttSnake, fatttsnake@gmail.com
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Schema(description = "已验证", example = "true")
|
||||||
|
val verified: Boolean?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locking
|
* Locking
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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(36) null comment '验证信息',
|
verify varchar(50) 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 '认证过期时间',
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
<select id="selectListWithRoleInfoByIds" resultMap="userWithRoleInfoMap">
|
<select id="selectListWithRoleInfoByIds" resultMap="userWithRoleInfoMap">
|
||||||
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.verify as user_verify,
|
||||||
t_user.locking as user_locking,
|
t_user.locking as user_locking,
|
||||||
t_user.expiration as user_expiration,
|
t_user.expiration as user_expiration,
|
||||||
t_user.credentials_expiration as user_credentials_expiration,
|
t_user.credentials_expiration as user_credentials_expiration,
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
<select id="selectOneWithRoleInfoById" resultMap="userWithRoleInfoMap">
|
<select id="selectOneWithRoleInfoById" resultMap="userWithRoleInfoMap">
|
||||||
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.verify as user_verify,
|
||||||
t_user.locking as user_locking,
|
t_user.locking as user_locking,
|
||||||
t_user.expiration as user_expiration,
|
t_user.expiration as user_expiration,
|
||||||
t_user.credentials_expiration as user_credentials_expiration,
|
t_user.credentials_expiration as user_credentials_expiration,
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
<select id="selectListWithInfo" resultMap="userWithInfoMap">
|
<select id="selectListWithInfo" resultMap="userWithInfoMap">
|
||||||
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.verify as user_verify,
|
||||||
t_user.locking as user_locking,
|
t_user.locking as user_locking,
|
||||||
t_user.expiration as user_expiration,
|
t_user.expiration as user_expiration,
|
||||||
t_user.credentials_expiration as user_credentials_expiration,
|
t_user.credentials_expiration as user_credentials_expiration,
|
||||||
@@ -282,6 +282,7 @@
|
|||||||
<resultMap id="userBaseMap" type="user">
|
<resultMap id="userBaseMap" type="user">
|
||||||
<id property="id" column="user_id"/>
|
<id property="id" column="user_id"/>
|
||||||
<result property="username" column="user_username"/>
|
<result property="username" column="user_username"/>
|
||||||
|
<result property="verify" column="user_verify"/>
|
||||||
<result property="locking" column="user_locking"/>
|
<result property="locking" column="user_locking"/>
|
||||||
<result property="expiration" column="user_expiration"/>
|
<result property="expiration" column="user_expiration"/>
|
||||||
<result property="credentialsExpiration" column="user_credentials_expiration"/>
|
<result property="credentialsExpiration" column="user_credentials_expiration"/>
|
||||||
|
|||||||
85
src/main/resources/templates/email-verify-account-cn.vm
Normal file
85
src/main/resources/templates/email-verify-account-cn.vm
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<!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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.verify-button {
|
||||||
|
display: flex;
|
||||||
|
margin: 20px;
|
||||||
|
padding-bottom: 60px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
.verify-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">账 号 激 活</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 class="verify-button"><a target="_blank" href=${verifyUrl}>验证邮箱</a></div>
|
||||||
|
<div>如果以上按钮无法点击,请复制此链接到浏览器地址栏中访问:<a target="_blank" href=${verifyUrl}>${verifyUrl}</a></div>
|
||||||
|
<div class="not-reply">此邮件由系统自动发送,请勿回复!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user