Optimize code. Add kdoc.

This commit is contained in:
2023-12-25 17:27:50 +08:00
parent 31358107c7
commit 15f631971c
27 changed files with 188 additions and 36 deletions

View File

@@ -5,14 +5,12 @@ import org.aspectj.lang.annotation.AfterReturning
import org.aspectj.lang.annotation.Aspect import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut import org.aspectj.lang.annotation.Pointcut
import org.aspectj.lang.reflect.MethodSignature import org.aspectj.lang.reflect.MethodSignature
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import top.fatweb.api.annotation.EventLogRecord import top.fatweb.api.annotation.EventLogRecord
import top.fatweb.api.entity.system.EventLog
import top.fatweb.api.service.system.IEventLogService import top.fatweb.api.service.system.IEventLogService
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.RegisterVo
/** /**
* Event log record aspect * Event log record aspect
@@ -25,24 +23,21 @@ import top.fatweb.api.vo.permission.LoginVo
class EventLogAspect( class EventLogAspect(
private val eventLogService: IEventLogService private val eventLogService: IEventLogService
) { ) {
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@Pointcut("@annotation(top.fatweb.api.annotation.EventLogRecord)") @Pointcut("@annotation(top.fatweb.api.annotation.EventLogRecord)")
fun eventLogPointcut() { fun eventLogPointcut() {
} }
@AfterReturning(value = "eventLogPointcut()", returning = "retValue") @AfterReturning(value = "eventLogPointcut()", returning = "retValue")
fun doAfter(joinPoint: JoinPoint, retValue: Any) { fun doAfter(joinPoint: JoinPoint, retValue: Any?) {
val annotation = (joinPoint.signature as MethodSignature).method.getAnnotation(EventLogRecord::class.java) val annotation = (joinPoint.signature as MethodSignature).method.getAnnotation(EventLogRecord::class.java)
try { val userId = WebUtil.getLoginUserId() ?: when (retValue) {
eventLogService.save(EventLog().apply { is LoginVo -> retValue.userId!!
this.event = annotation.event is RegisterVo -> retValue.userId!!
operateUserId = WebUtil.getLoginUserId() else -> -1
?: if (retValue is LoginVo) retValue.userId else -1
})
} catch (e: Exception) {
logger.error("Cannot record event!!!", e)
} }
eventLogService.saveEvent(annotation, userId)
} }
} }

View File

@@ -15,6 +15,7 @@ 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
import top.fatweb.api.vo.permission.RegisterVo
import top.fatweb.api.vo.permission.TokenVo import top.fatweb.api.vo.permission.TokenVo
/** /**
@@ -36,11 +37,11 @@ class AuthenticationController(
*/ */
@Operation(summary = "注册") @Operation(summary = "注册")
@PostMapping("/register") @PostMapping("/register")
fun register(@Valid @RequestBody registerParam: RegisterParam): ResponseResult<Nothing> { fun register(@Valid @RequestBody registerParam: RegisterParam): ResponseResult<RegisterVo> = ResponseResult.success(
authenticationService.register(registerParam) ResponseCode.PERMISSION_REGISTER_SUCCESS,
data = authenticationService.register(registerParam)
)
return ResponseResult.success(ResponseCode.PERMISSION_REGISTER_SUCCESS)
}
/** /**
* Send verify email * Send verify email

View File

@@ -7,11 +7,23 @@ import top.fatweb.api.properties.SecurityProperties
import top.fatweb.api.service.system.IStatisticsLogService import top.fatweb.api.service.system.IStatisticsLogService
import top.fatweb.api.util.RedisUtil import top.fatweb.api.util.RedisUtil
/**
* Statistics scheduled tasks
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Component @Component
class StatisticsCron( class StatisticsCron(
private val redisUtil: RedisUtil, private val redisUtil: RedisUtil,
private val statisticsLogService: IStatisticsLogService private val statisticsLogService: IStatisticsLogService
) { ) {
/**
* Auto record number of online users
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Scheduled(cron = "0 * * * * *") @Scheduled(cron = "0 * * * * *")
fun onlineUserCount() { fun onlineUserCount() {
statisticsLogService.save(StatisticsLog().apply { statisticsLogService.save(StatisticsLog().apply {

View File

@@ -18,7 +18,7 @@ import java.time.LocalDateTime
@TableName("t_event_log") @TableName("t_event_log")
class EventLog : Serializable { class EventLog : Serializable {
enum class Event(@field:EnumValue @field:JsonValue val code: String) { enum class Event(@field:EnumValue @field:JsonValue val code: String) {
LOGIN("LOGIN"), LOGOUT("LOGOUT"), REGISTER("REGISTER"), API("API") LOGIN("LOGIN"), LOGOUT("LOGOUT"), REGISTER("REGISTER"), VERIFY("VERIFY"), API("API")
} }
/** /**

View File

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

View File

@@ -1,5 +1,11 @@
package top.fatweb.api.exception package top.fatweb.api.exception
/**
* Email settings not configured exception
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class NoEmailConfigException( class NoEmailConfigException(
vararg configs: String vararg configs: String
) : RuntimeException("Email settings not configured: ${configs.joinToString(", ")}") ) : RuntimeException("Email settings not configured: ${configs.joinToString(", ")}")

View File

@@ -1,3 +1,9 @@
package top.fatweb.api.exception package top.fatweb.api.exception
/**
* No verification required exception
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class NoVerificationRequiredException : RuntimeException("No verification required") class NoVerificationRequiredException : RuntimeException("No verification required")

View File

@@ -1,3 +1,9 @@
package top.fatweb.api.exception package top.fatweb.api.exception
/**
* Verification code error or expired exception
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
class VerificationCodeErrorOrExpiredException : RuntimeException("Verification code is error or has expired") class VerificationCodeErrorOrExpiredException : RuntimeException("Verification code is error or has expired")

View File

@@ -4,5 +4,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
import top.fatweb.api.entity.system.EventLog import top.fatweb.api.entity.system.EventLog
/**
* Event log mapper
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Mapper @Mapper
interface EventLogMapper : BaseMapper<EventLog> interface EventLogMapper : BaseMapper<EventLog>

View File

@@ -4,5 +4,11 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper
import org.apache.ibatis.annotations.Mapper import org.apache.ibatis.annotations.Mapper
import top.fatweb.api.entity.system.StatisticsLog import top.fatweb.api.entity.system.StatisticsLog
/**
* Statistics log mapper
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Mapper @Mapper
interface StatisticsLogMapper : BaseMapper<StatisticsLog> interface StatisticsLogMapper : BaseMapper<StatisticsLog>

View File

@@ -86,6 +86,7 @@ data class UserAddParam(
* @since 1.0.0 * @since 1.0.0
*/ */
@Schema(description = "昵称") @Schema(description = "昵称")
@field:NotBlank(message = "Nickname can not be blank")
val nickname: String?, val nickname: String?,
/** /**

View File

@@ -3,6 +3,12 @@ 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
/**
* Get active information parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
data class ActiveInfoGetParam( data class ActiveInfoGetParam(
val scope: Scope = Scope.WEAK val scope: Scope = Scope.WEAK
) { ) {

View File

@@ -3,6 +3,12 @@ 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
/**
* Get online information parameters
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
data class OnlineInfoGetParam( data class OnlineInfoGetParam(
val scope: Scope = Scope.WEAK val scope: Scope = Scope.WEAK
) { ) {

View File

@@ -6,6 +6,7 @@ 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
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.TokenVo import top.fatweb.api.vo.permission.TokenVo
/** /**
@@ -21,7 +22,7 @@ interface IAuthenticationService {
* @author FatttSnake, fatttsnake@gmail.com * @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0 * @since 1.0.0
*/ */
fun register(registerParam: RegisterParam) fun register(registerParam: RegisterParam): RegisterVo
/** /**
* Send verify email * Send verify email

View File

@@ -34,6 +34,7 @@ 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.RegisterVo
import top.fatweb.api.vo.permission.TokenVo import top.fatweb.api.vo.permission.TokenVo
import java.io.StringWriter import java.io.StringWriter
import java.time.Instant import java.time.Instant
@@ -63,13 +64,14 @@ class AuthenticationServiceImpl(
) : IAuthenticationService { ) : IAuthenticationService {
private val logger: Logger = LoggerFactory.getLogger(this::class.java) private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@EventLogRecord(EventLog.Event.REGISTER)
@Transactional @Transactional
override fun register(registerParam: RegisterParam) { override fun register(registerParam: RegisterParam): RegisterVo {
val user = User().apply { val user = User().apply {
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()}" "${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
locking = 0 locking = 0
enable = 1 enable = 1
} }
@@ -82,6 +84,8 @@ class AuthenticationServiceImpl(
}) })
sendVerifyMail(user.username!!, "http://localhost:5173/verify?code=${user.verify!!}", registerParam.email!!) sendVerifyMail(user.username!!, "http://localhost:5173/verify?code=${user.verify!!}", registerParam.email!!)
return RegisterVo(userId = user.id)
} }
@Transactional @Transactional
@@ -91,7 +95,7 @@ class AuthenticationServiceImpl(
user.verify ?: throw NoVerificationRequiredException() user.verify ?: throw NoVerificationRequiredException()
user.verify = user.verify =
"${LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()}-${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)
@@ -113,11 +117,12 @@ class AuthenticationServiceImpl(
template.merge(velocityContext, stringWriter) template.merge(velocityContext, stringWriter)
MailUtil.sendSimpleMail( MailUtil.sendSimpleMail(
"激活您的账号", stringWriter.toString(), true, "验证您的账号", stringWriter.toString(), true,
email email
) )
} }
@EventLogRecord(EventLog.Event.VERIFY)
@Transactional @Transactional
override fun verify(verifyParam: VerifyParam) { override fun verify(verifyParam: VerifyParam) {
val user = userService.getById(WebUtil.getLoginUserId()) ?: throw AccessDeniedException("Access Denied") val user = userService.getById(WebUtil.getLoginUserId()) ?: throw AccessDeniedException("Access Denied")

View File

@@ -28,6 +28,7 @@ import top.fatweb.api.vo.permission.UserWithPasswordRoleInfoVo
import top.fatweb.api.vo.permission.UserWithRoleInfoVo import top.fatweb.api.vo.permission.UserWithRoleInfoVo
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import java.util.*
/** /**
* User service implement * User service implement
@@ -111,9 +112,12 @@ class UserServiceImpl(
user.apply { user.apply {
password = passwordEncoder.encode(rawPassword) password = passwordEncoder.encode(rawPassword)
verify = if (userAddParam.verified) null else "${
LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()
}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
} }
if (baseMapper.insert(user) == 1) { if (this.save(user)) {
user.userInfo?.let { userInfoService.save(it.apply { userId = user.id }) } user.userInfo?.let { userInfoService.save(it.apply { userId = user.id }) }
if (!user.roles.isNullOrEmpty()) { if (!user.roles.isNullOrEmpty()) {
@@ -175,9 +179,16 @@ class UserServiceImpl(
removeGroupIds.removeAll(addGroupIds) removeGroupIds.removeAll(addGroupIds)
oldGroupList.toSet().let { addGroupIds.removeAll(it) } oldGroupList.toSet().let { addGroupIds.removeAll(it) }
baseMapper.updateById(user) this.updateById(user)
baseMapper.update( this.update(
KtUpdateWrapper(User()).eq(User::id, user.id).set(User::expiration, user.expiration) KtUpdateWrapper(User()).eq(User::id, user.id)
.set(
User::verify,
if (userUpdateParam.verified) null else "${
LocalDateTime.now(ZoneOffset.UTC).toInstant(ZoneOffset.UTC).toEpochMilli()
}-${UUID.randomUUID()}-${UUID.randomUUID()}-${UUID.randomUUID()}"
)
.set(User::expiration, user.expiration)
.set(User::credentialsExpiration, user.credentialsExpiration) .set(User::credentialsExpiration, user.credentialsExpiration)
) )
@@ -230,7 +241,7 @@ class UserServiceImpl(
throw AccessDeniedException("Access denied") throw AccessDeniedException("Access denied")
} }
val user = baseMapper.selectById(userUpdatePasswordParam.id) val user = this.getById(userUpdatePasswordParam.id)
user?.let { user?.let {
val wrapper = KtUpdateWrapper(User()) val wrapper = KtUpdateWrapper(User())
wrapper.eq(User::id, user.id) wrapper.eq(User::id, user.id)
@@ -265,7 +276,7 @@ class UserServiceImpl(
return return
} }
baseMapper.deleteBatchIds(ids) this.removeBatchByIds(ids)
userInfoService.remove(KtQueryWrapper(UserInfo()).`in`(UserInfo::userId, ids)) userInfoService.remove(KtQueryWrapper(UserInfo()).`in`(UserInfo::userId, ids))
userRoleService.remove(KtQueryWrapper(UserRole()).`in`(UserRole::userId, ids)) userRoleService.remove(KtQueryWrapper(UserRole()).`in`(UserRole::userId, ids))
userGroupService.remove(KtQueryWrapper(UserGroup()).`in`(UserGroup::userId, ids)) userGroupService.remove(KtQueryWrapper(UserGroup()).`in`(UserGroup::userId, ids))

View File

@@ -1,6 +1,15 @@
package top.fatweb.api.service.system package top.fatweb.api.service.system
import com.baomidou.mybatisplus.extension.service.IService import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.api.annotation.EventLogRecord
import top.fatweb.api.entity.system.EventLog import top.fatweb.api.entity.system.EventLog
interface IEventLogService : IService<EventLog> /**
* Event log service interface
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
interface IEventLogService : IService<EventLog> {
fun saveEvent(annotation: EventLogRecord, userId: Long)
}

View File

@@ -3,4 +3,10 @@ package top.fatweb.api.service.system
import com.baomidou.mybatisplus.extension.service.IService import com.baomidou.mybatisplus.extension.service.IService
import top.fatweb.api.entity.system.StatisticsLog import top.fatweb.api.entity.system.StatisticsLog
/**
* Statistics log service interface
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
interface IStatisticsLogService : IService<StatisticsLog> interface IStatisticsLogService : IService<StatisticsLog>

View File

@@ -2,11 +2,30 @@ package top.fatweb.api.service.system.impl
import com.baomidou.dynamic.datasource.annotation.DS import com.baomidou.dynamic.datasource.annotation.DS
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import top.fatweb.api.annotation.EventLogRecord
import top.fatweb.api.entity.system.EventLog import top.fatweb.api.entity.system.EventLog
import top.fatweb.api.mapper.system.EventLogMapper import top.fatweb.api.mapper.system.EventLogMapper
import top.fatweb.api.service.system.IEventLogService import top.fatweb.api.service.system.IEventLogService
@DS("sqlite") @DS("sqlite")
@Service @Service
class EventLogServiceImpl : ServiceImpl<EventLogMapper, EventLog>(), IEventLogService class EventLogServiceImpl : ServiceImpl<EventLogMapper, EventLog>(), IEventLogService {
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
@Transactional(propagation = Propagation.REQUIRES_NEW)
override fun saveEvent(annotation: EventLogRecord, userId: Long) {
try {
this.save(EventLog().apply {
this.event = annotation.event
operateUserId = userId
})
} catch (e: Exception) {
logger.error("Cannot record event!!!", e)
}
}
}

View File

@@ -223,6 +223,6 @@ class StatisticsServiceImpl(
) )
} }
return ActiveInfoVo(getHistory("REGISTER"), getHistory("LOGIN")) return ActiveInfoVo(getHistory("REGISTER"), getHistory("LOGIN"), getHistory("VERIFY"))
} }
} }

View File

@@ -3,6 +3,12 @@ package top.fatweb.api.settings
import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.annotation.JsonValue
/**
* Type of mail security
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
enum class MailSecurityType(val code: String) { enum class MailSecurityType(val code: String) {
NONE("None"), NONE("None"),
SSL_TLS("SSL/TLS"), SSL_TLS("SSL/TLS"),

View File

@@ -8,6 +8,12 @@ import top.fatweb.api.settings.MailSettings
import top.fatweb.api.settings.SettingsOperator import top.fatweb.api.settings.SettingsOperator
import java.util.* import java.util.*
/**
* Mail util
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
object MailUtil { object MailUtil {
private val mailSender: JavaMailSenderImpl = JavaMailSenderImpl() private val mailSender: JavaMailSenderImpl = JavaMailSenderImpl()

View File

@@ -0,0 +1,24 @@
package top.fatweb.api.vo.permission
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import io.swagger.v3.oas.annotations.media.Schema
/**
* Register value object
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "注册返回参数")
data class RegisterVo(
/**
* User ID
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
@Schema(description = "User ID", example = "1709986058679975938")
@JsonSerialize(using = ToStringSerializer::class)
val userId: Long?
)

View File

@@ -25,7 +25,15 @@ data class ActiveInfoVo(
* @since 1.0.0 * @since 1.0.0
* @see HistoryVo * @see HistoryVo
*/ */
val loginHistory: List<HistoryVo> val loginHistory: List<HistoryVo>,
/**
* Verify user number history
*
* @author FatttSnake, fatttsnake@gmail.com
* @since 1.0.0
*/
val verifyHistory: List<HistoryVo>
) { ) {
data class HistoryVo( data class HistoryVo(
/** /**

View File

@@ -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(50) null comment '验证信息', verify 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 '认证过期时间',

View File

@@ -4,7 +4,7 @@ create table if not exists t_user_info
( (
id bigint not null primary key, id bigint not null primary key,
user_id bigint not null comment '用户ID', user_id bigint not null comment '用户ID',
nickname varchar(50) null comment '昵称', nickname varchar(50) not null comment '昵称',
avatar text null comment '头像', avatar text null comment '头像',
email varchar(100) not 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 '创建时间',

View File

@@ -5,7 +5,7 @@
<meta name="viewport" <meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> 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"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>激活您的账号</title> <title>验证您的账号</title>
<style> <style>
* { * {
margin: 0; margin: 0;