diff --git a/src/main/kotlin/com/xlf/dromstarkotlin/controllers/TokenController.kt b/src/main/kotlin/com/xlf/dromstarkotlin/controllers/TokenController.kt new file mode 100644 index 0000000..a6e3ca1 --- /dev/null +++ b/src/main/kotlin/com/xlf/dromstarkotlin/controllers/TokenController.kt @@ -0,0 +1,54 @@ +package com.xlf.dromstarkotlin.controllers + +import com.frontleaves.general.utils.BaseResponse +import com.frontleaves.general.utils.ResultUtil +import com.xlf.dromstarkotlin.services.TokenService +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.CookieValue +import org.springframework.web.bind.annotation.CrossOrigin +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/api/token") +@CrossOrigin(origins = ["*"]) +class TokenController( + private val tokenService: TokenService +) { + /** + * 创建 Token 组件 + */ + @PostMapping("/create") + fun createToken( + @CookieValue("session") token: String?, httpServletResponse: HttpServletResponse, + @RequestParam("return") returnLink: String?, + httpServletRequest: HttpServletRequest + ): ResponseEntity { + // 检查 token 是否存在 + return if (token == null) { + val newToken = tokenService.tokenCreate(httpServletRequest) + httpServletResponse.addCookie( + Cookie("session", newToken) + .also { it.path = "/" } + .also { it.maxAge = 43200 } + ) + ResultUtil.redirect("SuccessCreate", "Token创建成功", newToken, returnLink, httpServletRequest) + } else { + if (tokenService.tokenVerify(token, httpServletResponse)) { + ResultUtil.redirect("StillValid", "Token依旧有效", null, returnLink, httpServletRequest) + } else { + httpServletResponse.addCookie( + Cookie("session", null) + .also { it.path = "/" } + .also { it.maxAge = 0 } + ) + this.createToken(null, httpServletResponse, returnLink, httpServletRequest) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/xlf/dromstarkotlin/controllers/UserController.kt b/src/main/kotlin/com/xlf/dromstarkotlin/controllers/UserController.kt new file mode 100644 index 0000000..20691d2 --- /dev/null +++ b/src/main/kotlin/com/xlf/dromstarkotlin/controllers/UserController.kt @@ -0,0 +1,55 @@ +package com.xlf.dromstarkotlin.controllers + +import com.frontleaves.general.utils.BaseResponse +import com.frontleaves.general.utils.ErrorCode +import com.frontleaves.general.utils.ResultUtil +import com.xlf.dromstarkotlin.entity.voData.SignInVO +import com.xlf.dromstarkotlin.exception.BusinessException +import com.xlf.dromstarkotlin.services.TokenService +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.CookieValue +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController + +/** + * 用户控制器 + * + * 对用户进行管理和操作 + * + * @author 筱锋xiao_lfeng + * @since v1.0.0 + */ +@RestController +@RequestMapping("/api/user") +class UserController( + private val tokenService: TokenService +) { + /** + * 用户登录组件 + */ + @PostMapping("/sign/in") + fun signIn( + signInVO: SignInVO?, @CookieValue("sessionId") token: String?, httpServletResponse: HttpServletResponse, + httpServletRequest: HttpServletRequest + ): ResponseEntity? { + if (signInVO == null) { + return BusinessException().backInfo(ErrorCode.MISSING_REQUEST_BODY, httpServletRequest) + } else { + // 对 Token 进行校验 + if (token != null) { + // 对 token 进行校验 + if (!tokenService.tokenVerify(token, httpServletResponse)) { + // 校验失败 + return BusinessException().backInfo(ErrorCode.TOKEN_VERIFY_FAILED, httpServletRequest) + } + } else { + // 跳转至创建 Token 页面 + return ResultUtil.redirect("/api/token/create", httpServletRequest) + } + } + return null + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/xlf/dromstarkotlin/services/PreOperation.kt b/src/main/kotlin/com/xlf/dromstarkotlin/services/PreOperation.kt new file mode 100644 index 0000000..33cd09d --- /dev/null +++ b/src/main/kotlin/com/xlf/dromstarkotlin/services/PreOperation.kt @@ -0,0 +1,45 @@ +package com.xlf.dromstarkotlin.services + +import com.frontleaves.general.utils.ErrorCode +import com.xlf.dromstarkotlin.cache.CacheData +import com.xlf.dromstarkotlin.cache.CacheToken +import com.xlf.dromstarkotlin.exception.BusinessException +import jakarta.servlet.http.HttpServletRequest +import org.aspectj.lang.ProceedingJoinPoint +import org.aspectj.lang.annotation.Around +import org.aspectj.lang.annotation.Aspect +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Component +import org.springframework.web.context.request.RequestContextHolder +import org.springframework.web.context.request.ServletRequestAttributes +import java.util.Date + +@Aspect +@Component +class PreOperation { + + /** + * 对用户访问量进行控制 + */ + @Around("execution(* com.xlf.dromstarkotlin.controllers.TokenController.*(..))") + fun tokenCheckService(proceedingJoinPoint: ProceedingJoinPoint): ResponseEntity? { + val requestAttributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? + val httpServletRequest = requestAttributes?.request as HttpServletRequest + // 检查 + if (CacheData.tokenVisits[httpServletRequest.remoteAddr] == null) { + CacheData.tokenVisits[httpServletRequest.remoteAddr] = CacheToken(1, Date().time) + } else { + if (CacheData.tokenVisits[httpServletRequest.remoteAddr]!!.timestamp + 60000 > Date().time) { + if (CacheData.tokenVisits[httpServletRequest.remoteAddr]!!.count > 20L) { + return BusinessException().backInfo(ErrorCode.QPS_LIMITATION_VISIT, httpServletRequest) + } else { + CacheData.tokenVisits[httpServletRequest.remoteAddr]!!.count += 1 + } + } else { + // 删除内容 + CacheData.tokenVisits.remove(httpServletRequest.remoteAddr) + } + } + return proceedingJoinPoint.proceed() as ResponseEntity<*>? + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/xlf/dromstarkotlin/services/ScheduleService.kt b/src/main/kotlin/com/xlf/dromstarkotlin/services/ScheduleService.kt new file mode 100644 index 0000000..f7a003e --- /dev/null +++ b/src/main/kotlin/com/xlf/dromstarkotlin/services/ScheduleService.kt @@ -0,0 +1,43 @@ +package com.xlf.dromstarkotlin.services + +import com.xlf.dromstarkotlin.mapper.TokenMapper +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component +import java.text.SimpleDateFormat +import java.util.* + +@Component +class ScheduleService( + val tokenMapper: TokenMapper +) { + + @Scheduled(fixedDelay = 900000) + fun clearExpiredToken() { + // 循环所有 Token 信息 + val getToken = tokenMapper.getAllToken() + var i = 0 + if (getToken != null) { + for (tokenInfo in getToken) { + if (tokenInfo.userId != null) { + if (tokenInfo.updatedAt != null) { + if (tokenInfo.updatedAt!!.time + 43200000 < Date().time) { + tokenMapper.deleteToken(tokenInfo.token!!) + i++ + } + } else { + if (tokenInfo.createdAt!!.time + 43200000 < Date().time) { + tokenMapper.deleteToken(tokenInfo.token!!) + i++ + } + } + } else { + if (tokenInfo.createdAt!!.time + 300000 < Date().time) { + tokenMapper.deleteToken(tokenInfo.token!!) + i++ + } + } + } + } + println("${SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())} [Log] <----->Scheduled | Message:clear token $i") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/xlf/dromstarkotlin/services/TokenService.kt b/src/main/kotlin/com/xlf/dromstarkotlin/services/TokenService.kt new file mode 100644 index 0000000..f8723cb --- /dev/null +++ b/src/main/kotlin/com/xlf/dromstarkotlin/services/TokenService.kt @@ -0,0 +1,94 @@ +package com.xlf.dromstarkotlin.services + +import com.xlf.dromstarkotlin.entity.doData.TokenDO +import com.xlf.dromstarkotlin.mapper.TokenMapper +import com.xlf.dromstarkotlin.utils.Processing +import jakarta.servlet.http.Cookie +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import java.sql.Timestamp +import java.util.Date + +/** + * Token 控制器 + * + * 对本系统的进行授权操作 + * + * @since v1.0.0 + * @author 筱锋xiao_lfeng + */ +@Service +class TokenService( + private val tokenMapper: TokenMapper +) { + /** + * 对 token 进行校验 + */ + @Transactional + fun tokenVerify(token: String, httpServletResponse: HttpServletResponse): Boolean { + val tokenDO = tokenMapper.getToken(token) + // 判断 token 是否存在 + if (tokenDO != null) { + // 已登录用户 + if (tokenDO.userId != null) { + // 判断 token 是否过期 + if (tokenDO.updatedAt != null) { + if (tokenDO.updatedAt!!.time + 43200000 > Date().time) { + return true + } else { + tokenMapper.deleteToken(token) + val cookie = Cookie("session", null).also { it.maxAge = 0 }.also { it.path = "/" } + httpServletResponse.addCookie(cookie) + } + } else { + if (tokenDO.createdAt!!.time + 43200000 > Date().time) { + return true + } else { + tokenMapper.deleteToken(token) + val cookie = Cookie("session", null).also { it.maxAge = 0 }.also { it.path = "/" } + httpServletResponse.addCookie(cookie) + } + } + } else { + // 未登录用户 + if (tokenDO.createdAt!!.time + 300000 > Date().time) { + return true + } else { + tokenMapper.deleteToken(token) + val cookie = Cookie("session", null).also { it.maxAge = 0 }.also { it.path = "/" } + httpServletResponse.addCookie(cookie) + } + } + } + return false + } + + /** + * 创建 token + */ + @Transactional + fun tokenCreate(httpServletRequest: HttpServletRequest): String { + var token: String + var tokenDO: TokenDO? + do { + // 创建 token + token = Processing.createToken() + // 检查 token 是否存在 + tokenDO = tokenMapper.getToken(token) + } while (tokenDO != null) + // 不存在则创建 + val newTokenDO = TokenDO( + null, + null, + token, + httpServletRequest.getHeader("User-Agent"), + httpServletRequest.localAddr, + Timestamp(Date().time), + null + ) + tokenMapper.insertToken(newTokenDO) + return token + } +} \ No newline at end of file