feat(新增): 刷新token
This commit is contained in:
parent
c8e14fca20
commit
15176c0b5a
|
@ -5,6 +5,8 @@ import cn.bunny.dao.pojo.result.Result
|
|||
import cn.bunny.dao.pojo.result.ResultCodeEnum
|
||||
import lombok.extern.slf4j.Slf4j
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.springframework.validation.FieldError
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.ResponseBody
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
@ -12,6 +14,7 @@ import java.io.FileNotFoundException
|
|||
import java.nio.file.AccessDeniedException
|
||||
import java.sql.SQLIntegrityConstraintViolationException
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
@RestControllerAdvice
|
||||
|
@ -29,6 +32,13 @@ class GlobalExceptionHandler {
|
|||
return Result.error(null, code, exception.message)
|
||||
}
|
||||
|
||||
// 表单验证字段
|
||||
@ExceptionHandler(MethodArgumentNotValidException::class)
|
||||
fun handleValidationExceptions(ex: MethodArgumentNotValidException): Result<String> {
|
||||
val errorMessage = ex.bindingResult.fieldErrors.stream().map { obj: FieldError -> obj.defaultMessage }.collect(Collectors.joining(", "))
|
||||
return Result.error(null, 201, errorMessage)
|
||||
}
|
||||
|
||||
// 运行时异常信息
|
||||
@ExceptionHandler(RuntimeException::class)
|
||||
@ResponseBody
|
||||
|
@ -47,7 +57,7 @@ class GlobalExceptionHandler {
|
|||
|
||||
// 错误消息
|
||||
val message = exception.message ?: ""
|
||||
|
||||
|
||||
// 匹配到内容
|
||||
val patternString = "Request method '(\\w+)' is not supported"
|
||||
val matcher = Pattern.compile(patternString).matcher(message)
|
||||
|
|
|
@ -53,6 +53,11 @@
|
|||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.14</version>
|
||||
</dependency>
|
||||
<!-- 表单验证 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit5</artifactId>
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
package cn.bunny.dao.dto.system
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import jakarta.validation.constraints.NotBlank
|
||||
import lombok.AllArgsConstructor
|
||||
import lombok.Data
|
||||
import lombok.NoArgsConstructor
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Schema(name = "RefreshTokenDto对象", title = "登录成功返回内容", description = "登录成功返回内容")
|
||||
class RefreshTokenDto {
|
||||
@Schema(name = "refreshToken", title = "请求刷新token")
|
||||
@NotBlank(message = "请求刷新token不能为空")
|
||||
var refreshToken: String? = null
|
||||
|
||||
@Schema(name = "readMeDay", title = "记住我天数")
|
||||
var readMeDay: Long = 1
|
||||
}
|
|
@ -7,5 +7,6 @@ class LocalDateTimeConstant {
|
|||
companion object {
|
||||
const val YYYY_MM_DD: String = "yyyy-MM-dd"
|
||||
const val YYYY_MM_DD_HH_MM_SS: String = "yyyy-MM-dd HH:mm:ss"
|
||||
const val YYYY_MM_DD_HH_MM_SS_SLASH: String = "yyyy/MM/dd HH:mm:ss"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ enum class ResultCodeEnum(val code: Int, val message: String) {
|
|||
// 成功操作 200
|
||||
SUCCESS(200, "操作成功"),
|
||||
SUCCESS_LOGOUT(200, "退出成功"),
|
||||
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
|
||||
EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"),
|
||||
|
||||
// 验证错误 201
|
||||
USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"),
|
||||
|
@ -18,7 +18,6 @@ enum class ResultCodeEnum(val code: Int, val message: String) {
|
|||
EMAIL_CODE_EMPTY(201, "邮箱验证码过期或不存在"),
|
||||
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
|
||||
LOGIN_ERROR(201, "账号或密码错误"),
|
||||
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
|
||||
SEND_MAIL_CODE_ERROR(201, "邮件发送失败"),
|
||||
|
||||
// 数据相关 206
|
||||
|
@ -31,11 +30,9 @@ enum class ResultCodeEnum(val code: Int, val message: String) {
|
|||
// 身份过期 208
|
||||
LOGIN_AUTH(208, "请先登陆"),
|
||||
AUTHENTICATION_EXPIRED(208, "身份验证过期"),
|
||||
SESSION_EXPIRATION(208, "会话过期"),
|
||||
|
||||
// 封禁 209
|
||||
FAIL_NO_ACCESS_DENIED_USER_LOCKED(209, "账户已封禁"),
|
||||
THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"),
|
||||
|
||||
// 提示错误
|
||||
URL_ENCODE_ERROR(216, "URL编码失败"),
|
||||
|
@ -45,7 +42,6 @@ enum class ResultCodeEnum(val code: Int, val message: String) {
|
|||
// 无权访问 403
|
||||
FAIL_REQUEST_NOT_AUTH(403, "用户未认证"),
|
||||
FAIL_NO_ACCESS_DENIED(403, "无权访问"),
|
||||
FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"),
|
||||
LOGGED_IN_FROM_ANOTHER_DEVICE(403, "没有权限访问"),
|
||||
|
||||
// 系统错误 500
|
||||
|
|
|
@ -54,7 +54,7 @@ data class LoginVo(
|
|||
var refreshToken: String? = null,
|
||||
|
||||
@Schema(name = "expires", title = "过期时间")
|
||||
var expires: Long? = null,
|
||||
var expires: String? = null,
|
||||
|
||||
@Schema(name = "roleList", title = "角色列表")
|
||||
var roleList: List<String>? = null,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package cn.bunny.dao.vo.user
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema
|
||||
import lombok.AllArgsConstructor
|
||||
import lombok.Builder
|
||||
import lombok.NoArgsConstructor
|
||||
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "RefreshTokenVo 对象", title = "刷新token返回内容", description = "刷新token返回内容")
|
||||
data class RefreshTokenVo(
|
||||
@Schema(name = "accessToken", title = "访问令牌")
|
||||
var accessToken: String? = null,
|
||||
|
||||
@Schema(name = "refreshToken", title = "刷新token")
|
||||
var refreshToken: String? = null,
|
||||
|
||||
@Schema(name = "expires", title = "过期时间")
|
||||
var expires: String? = null,
|
||||
)
|
|
@ -1,11 +1,16 @@
|
|||
package cn.bunny.services.controller
|
||||
|
||||
import cn.bunny.dao.dto.system.RefreshTokenDto
|
||||
import cn.bunny.dao.pojo.result.Result
|
||||
import cn.bunny.dao.pojo.result.ResultCodeEnum
|
||||
import cn.bunny.dao.vo.user.RefreshTokenVo
|
||||
import cn.bunny.services.service.UserService
|
||||
import io.swagger.v3.oas.annotations.Operation
|
||||
import io.swagger.v3.oas.annotations.tags.Tag
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
|
@ -22,7 +27,6 @@ import org.springframework.web.bind.annotation.RestController
|
|||
@RequestMapping("/admin/user")
|
||||
@Tag(name = "系统用户", description = "系统用户相关接口")
|
||||
class UserController {
|
||||
|
||||
@Autowired
|
||||
private lateinit var userService: UserService
|
||||
|
||||
|
@ -30,6 +34,13 @@ class UserController {
|
|||
@PostMapping("noAuth/sendLoginEmail")
|
||||
fun sendLoginEmail(email: String): Result<String> {
|
||||
userService.sendLoginEmail(email)
|
||||
return Result.success()
|
||||
return Result.success(ResultCodeEnum.EMAIL_CODE_SEND_SUCCESS)
|
||||
}
|
||||
|
||||
@Operation(summary = "刷新token", description = "刷新用户token")
|
||||
@PostMapping("noAuth/refreshToken")
|
||||
fun refreshToken(@Valid @RequestBody dto: RefreshTokenDto): Result<RefreshTokenVo> {
|
||||
val vo: RefreshTokenVo = userService.refreshToken(dto)
|
||||
return Result.success(vo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import cn.bunny.common.service.utils.ip.IpUtil
|
|||
import cn.bunny.dao.entity.system.AdminUser
|
||||
import cn.bunny.dao.entity.system.Power
|
||||
import cn.bunny.dao.entity.system.Role
|
||||
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant
|
||||
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminLoginInfoPrefix
|
||||
import cn.bunny.dao.pojo.constant.RedisUserConstant.Companion.getAdminUserEmailCodePrefix
|
||||
import cn.bunny.dao.vo.user.LoginVo
|
||||
|
@ -15,6 +16,8 @@ import org.springframework.beans.factory.annotation.Autowired
|
|||
import org.springframework.data.redis.core.RedisTemplate
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Component
|
||||
|
@ -41,6 +44,12 @@ class UserFactory {
|
|||
updateUser.lastLoginIp = IpUtil.getCurrentUserIpAddress().remoteAddr
|
||||
updateUser.lastLoginIpAddress = IpUtil.getCurrentUserIpAddress().ipRegion
|
||||
|
||||
// 计算过期时间,并格式化返回
|
||||
val localDateTime: LocalDateTime = LocalDateTime.now()
|
||||
val plusDay = localDateTime.plusDays(readMeDay)
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS_SLASH)
|
||||
val expires: String = plusDay.format(dateTimeFormatter)
|
||||
|
||||
// 构建返回对象
|
||||
val loginVo = LoginVo()
|
||||
BeanUtils.copyProperties(user, loginVo)
|
||||
|
@ -51,7 +60,7 @@ class UserFactory {
|
|||
loginVo.roleList = roleMapper.selectListByUserId(userId).map { role: Role -> role.roleCode!! }
|
||||
loginVo.powerList = powerMapper.selectListByUserId(userId).map { power: Power -> power.powerCode!! }
|
||||
loginVo.updateUser = userId
|
||||
loginVo.expires = readMeDay
|
||||
loginVo.expires = expires
|
||||
|
||||
// 将信息保存在Redis中
|
||||
redisTemplate.opsForValue().set(getAdminLoginInfoPrefix(email), loginVo, readMeDay, TimeUnit.DAYS)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cn.bunny.services.service;
|
||||
|
||||
import cn.bunny.dao.dto.system.RefreshTokenDto;
|
||||
import cn.bunny.dao.entity.system.AdminUser;
|
||||
import cn.bunny.dao.vo.user.RefreshTokenVo;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@ -20,4 +22,13 @@ public interface UserService extends IService<AdminUser> {
|
|||
* @param email 邮箱
|
||||
*/
|
||||
void sendLoginEmail(@NotNull String email);
|
||||
|
||||
/**
|
||||
* 刷新用户token
|
||||
*
|
||||
* @param dto 请求token
|
||||
* @return 刷新token返回内容
|
||||
*/
|
||||
@NotNull
|
||||
RefreshTokenVo refreshToken(@NotNull RefreshTokenDto dto);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
package cn.bunny.services.service.impl
|
||||
|
||||
import cn.bunny.common.service.exception.BunnyException
|
||||
import cn.bunny.common.service.utils.JwtHelper
|
||||
import cn.bunny.dao.dto.system.RefreshTokenDto
|
||||
import cn.bunny.dao.entity.system.AdminUser
|
||||
import cn.bunny.dao.entity.system.EmailUsers
|
||||
import cn.bunny.dao.pojo.constant.RedisUserConstant
|
||||
import cn.bunny.dao.pojo.email.EmailSendInit
|
||||
import cn.bunny.dao.pojo.result.ResultCodeEnum
|
||||
import cn.bunny.dao.vo.user.LoginVo
|
||||
import cn.bunny.dao.vo.user.RefreshTokenVo
|
||||
import cn.bunny.services.factory.EmailFactory
|
||||
import cn.bunny.services.factory.UserFactory
|
||||
import cn.bunny.services.mapper.EmailUsersMapper
|
||||
import cn.bunny.services.mapper.UserMapper
|
||||
import cn.bunny.services.service.UserService
|
||||
|
@ -27,6 +32,10 @@ import org.springframework.transaction.annotation.Transactional
|
|||
@Service
|
||||
@Transactional
|
||||
internal class UserServiceImpl : ServiceImpl<UserMapper?, AdminUser?>(), UserService {
|
||||
|
||||
@Autowired
|
||||
private lateinit var userFactory: UserFactory
|
||||
|
||||
@Autowired
|
||||
private lateinit var redisTemplate: RedisTemplate<Any, Any>
|
||||
|
||||
|
@ -57,5 +66,28 @@ internal class UserServiceImpl : ServiceImpl<UserMapper?, AdminUser?>(), UserSer
|
|||
// 将验证码存入Redis中
|
||||
redisTemplate.opsForValue().set(RedisUserConstant.getAdminUserEmailCodePrefix(email), emailCode)
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新用户token
|
||||
* @param dto 请求token
|
||||
* @return 刷新token返回内容
|
||||
*/
|
||||
override fun refreshToken(dto: RefreshTokenDto): RefreshTokenVo {
|
||||
// 解析token内容
|
||||
val userId = JwtHelper.getUserId(dto.refreshToken)
|
||||
val adminUser: AdminUser? = getOne(KtQueryWrapper(AdminUser()).eq(AdminUser::id, userId))
|
||||
|
||||
// 判断用户是否禁用
|
||||
when {
|
||||
adminUser?.status == 1.toByte() -> throw BunnyException(ResultCodeEnum.FAIL_NO_ACCESS_DENIED_USER_LOCKED)
|
||||
}
|
||||
|
||||
// 构建返回内容
|
||||
val buildUserVo: LoginVo = userFactory.buildUserVo(adminUser!!, dto.readMeDay)
|
||||
val refreshTokenVo = RefreshTokenVo()
|
||||
BeanUtils.copyProperties(buildUserVo, refreshTokenVo)
|
||||
|
||||
return refreshTokenVo
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package cn.bunny.services.service.impl
|
||||
|
||||
import cn.bunny.dao.entity.system.AdminUser
|
||||
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant
|
||||
import cn.bunny.services.mapper.UserMapper
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.context.junit4.SpringRunner
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
@SpringBootTest
|
||||
@RunWith(SpringRunner::class)
|
||||
|
@ -21,4 +24,14 @@ class UserServiceImplTest {
|
|||
adminUser.lastLoginIpAddress = "内网IP"
|
||||
userMapper.updateById(adminUser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTime() {
|
||||
val localDateTime: LocalDateTime = LocalDateTime.now()
|
||||
val plusDay = localDateTime.plusDays(7)
|
||||
val dateTimeFormatter = DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS_SLASH)
|
||||
val format = plusDay.format(dateTimeFormatter)
|
||||
|
||||
println(format)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue