Finish system log
This commit is contained in:
@@ -28,7 +28,7 @@ create table t_sys_log
|
|||||||
id bigint not null,
|
id bigint not null,
|
||||||
log_type varchar(50) not null comment '日志类型',
|
log_type varchar(50) not null comment '日志类型',
|
||||||
operate_user_id bigint not null comment '操作用户',
|
operate_user_id bigint not null comment '操作用户',
|
||||||
operate_time datetime not null default (utc_timestamp()) comment '操作时间',
|
operate_time datetime(3) not null default (utc_timestamp()) comment '操作时间',
|
||||||
request_uri varchar(500) default null comment '请求 URI',
|
request_uri varchar(500) default null comment '请求 URI',
|
||||||
request_method varchar(10) default null comment '请求方式',
|
request_method varchar(10) default null comment '请求方式',
|
||||||
request_params text comment '请求参数',
|
request_params text comment '请求参数',
|
||||||
@@ -36,12 +36,10 @@ create table t_sys_log
|
|||||||
request_server_address varchar(50) not null comment '请求服务器地址',
|
request_server_address varchar(50) not null comment '请求服务器地址',
|
||||||
is_exception char(1) default null comment '是否异常',
|
is_exception char(1) default null comment '是否异常',
|
||||||
exception_info text comment '异常信息',
|
exception_info text comment '异常信息',
|
||||||
start_time datetime not null comment '开始时间',
|
start_time datetime(3) not null comment '开始时间',
|
||||||
end_time datetime not null comment '结束时间',
|
end_time datetime(3) not null comment '结束时间',
|
||||||
execute_time int default null comment '执行时间',
|
execute_time bigint default null comment '执行时间',
|
||||||
user_agent varchar(500) default null comment '用户代理',
|
user_agent varchar(500) default null comment '用户代理',
|
||||||
device_name varchar(100) default null comment '操作系统',
|
|
||||||
browser_name varchar(100) default null comment '浏览器名称',
|
|
||||||
primary key (id) using btree,
|
primary key (id) using btree,
|
||||||
key idx_sys_log_log_type (log_type) using btree,
|
key idx_sys_log_log_type (log_type) using btree,
|
||||||
key idx_sys_log_operate_user_id (operate_user_id) using btree,
|
key idx_sys_log_operate_user_id (operate_user_id) using btree,
|
||||||
|
|||||||
15
src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt
Normal file
15
src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package top.fatweb.api.config
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
|
||||||
|
import top.fatweb.api.interceptor.SysLogInterceptor
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
class SysLogConfig(
|
||||||
|
private val sysLogInterceptor: SysLogInterceptor
|
||||||
|
) : WebMvcConfigurer {
|
||||||
|
override fun addInterceptors(registry: InterceptorRegistry) {
|
||||||
|
registry.addInterceptor(sysLogInterceptor).addPathPatterns("/**").excludePathPatterns("/error/thrown")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package top.fatweb.api.entity;
|
package top.fatweb.api.entity
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId
|
||||||
import com.baomidou.mybatisplus.annotation.TableName;
|
import com.baomidou.mybatisplus.annotation.TableName
|
||||||
import java.io.Serializable;
|
import java.io.Serializable
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -96,7 +96,7 @@ class SysLog : Serializable {
|
|||||||
* 执行时间
|
* 执行时间
|
||||||
*/
|
*/
|
||||||
@TableField("execute_time")
|
@TableField("execute_time")
|
||||||
var executeTime: Int? = null
|
var executeTime: Long? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户代理
|
* 用户代理
|
||||||
@@ -104,19 +104,7 @@ class SysLog : Serializable {
|
|||||||
@TableField("user_agent")
|
@TableField("user_agent")
|
||||||
var userAgent: String? = null
|
var userAgent: String? = null
|
||||||
|
|
||||||
/**
|
|
||||||
* 操作系统
|
|
||||||
*/
|
|
||||||
@TableField("device_name")
|
|
||||||
var deviceName: String? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 浏览器名称
|
|
||||||
*/
|
|
||||||
@TableField("browser_name")
|
|
||||||
var browserName: String? = null
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "SysLog(id=$id, logType=$logType, operateUserId=$operateUserId, operateTime=$operateTime, requestUri=$requestUri, requestMethod=$requestMethod, requestParams=$requestParams, requestIp=$requestIp, requestServerAddress=$requestServerAddress, isException=$isException, exceptionInfo=$exceptionInfo, startTime=$startTime, endTime=$endTime, executeTime=$executeTime, userAgent=$userAgent, deviceName=$deviceName, browserName=$browserName)"
|
return "SysLog(id=$id, logType=$logType, operateUserId=$operateUserId, operateTime=$operateTime, requestUri=$requestUri, requestMethod=$requestMethod, requestParams=$requestParams, requestIp=$requestIp, requestServerAddress=$requestServerAddress, isException=$isException, exceptionInfo=$exceptionInfo, startTime=$startTime, endTime=$endTime, executeTime=$executeTime, userAgent=$userAgent)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt
Normal file
110
src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package top.fatweb.api.interceptor
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest
|
||||||
|
import jakarta.servlet.http.HttpServletResponse
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.core.MethodParameter
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter
|
||||||
|
import org.springframework.http.server.ServerHttpRequest
|
||||||
|
import org.springframework.http.server.ServerHttpResponse
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
|
||||||
|
import top.fatweb.api.entity.SysLog
|
||||||
|
import top.fatweb.api.entity.common.ResponseResult
|
||||||
|
import top.fatweb.api.service.ISysLogService
|
||||||
|
import top.fatweb.api.util.WebUtil
|
||||||
|
import java.net.URI
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.temporal.ChronoUnit
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
class SysLogInterceptor(
|
||||||
|
val customThreadPoolTaskExecutor: Executor, val sysLogService: ISysLogService
|
||||||
|
) : HandlerInterceptor, ResponseBodyAdvice<Any> {
|
||||||
|
private val logger: Logger = LoggerFactory.getLogger(this::class.java)
|
||||||
|
private val sysLogThreadLocal = ThreadLocal<SysLog>()
|
||||||
|
private val resultThreadLocal = ThreadLocal<Any>()
|
||||||
|
|
||||||
|
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
|
||||||
|
val sysLog = SysLog().apply {
|
||||||
|
operateUserId = WebUtil.getLoginUserId() ?: -1
|
||||||
|
startTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
|
requestUri = URI(request.requestURI).path
|
||||||
|
requestParams = formatParams(request.parameterMap)
|
||||||
|
requestMethod = request.method
|
||||||
|
requestIp = request.remoteAddr
|
||||||
|
requestServerAddress = "${request.scheme}://${request.serverName}:${request.serverPort}"
|
||||||
|
userAgent = request.getHeader("User-Agent")
|
||||||
|
}
|
||||||
|
|
||||||
|
sysLogThreadLocal.set(sysLog)
|
||||||
|
|
||||||
|
logger.info("开始计时: {} URI: {} IP: {}", sysLog.startTime, sysLog.requestUri, sysLog.requestIp)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterCompletion(
|
||||||
|
request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?
|
||||||
|
) {
|
||||||
|
val sysLog = sysLogThreadLocal.get()
|
||||||
|
val result = resultThreadLocal.get()
|
||||||
|
sysLog.endTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
|
sysLog.executeTime = ChronoUnit.MILLIS.between(sysLog.startTime, sysLog.endTime)
|
||||||
|
if (result is ResponseResult<*>) {
|
||||||
|
if (result.success) {
|
||||||
|
sysLog.apply {
|
||||||
|
logType = "INFO"
|
||||||
|
isException = "N"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sysLog.apply {
|
||||||
|
logType = "ERROR"
|
||||||
|
isException = "Y"
|
||||||
|
exceptionInfo = result.msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customThreadPoolTaskExecutor.execute(SaveLogThread(sysLog, sysLogService))
|
||||||
|
}
|
||||||
|
sysLogThreadLocal.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatParams(parameterMap: Map<String, Array<String>>): String {
|
||||||
|
val params = StringJoiner("&")
|
||||||
|
|
||||||
|
parameterMap.forEach {
|
||||||
|
params.add("${it.key}=${if (it.key.endsWith("password", true)) "*" else it.value.joinToString(",")}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return params.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SaveLogThread(val sysLog: SysLog, val sysLogService: ISysLogService) : Thread() {
|
||||||
|
override fun run() {
|
||||||
|
sysLog.operateTime = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
|
sysLogService.save(sysLog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun supports(returnType: MethodParameter, converterType: Class<out HttpMessageConverter<*>>): Boolean =
|
||||||
|
true
|
||||||
|
|
||||||
|
override fun beforeBodyWrite(
|
||||||
|
body: Any?,
|
||||||
|
returnType: MethodParameter,
|
||||||
|
selectedContentType: MediaType,
|
||||||
|
selectedConverterType: Class<out HttpMessageConverter<*>>,
|
||||||
|
request: ServerHttpRequest,
|
||||||
|
response: ServerHttpResponse
|
||||||
|
): Any? {
|
||||||
|
resultThreadLocal.set(body)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service
|
|||||||
import top.fatweb.api.constant.SecurityConstants
|
import top.fatweb.api.constant.SecurityConstants
|
||||||
import top.fatweb.api.entity.permission.LoginUser
|
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.exception.TokenHasExpiredException
|
||||||
import top.fatweb.api.service.IUserService
|
import top.fatweb.api.service.IUserService
|
||||||
import top.fatweb.api.service.permission.IAuthenticationService
|
import top.fatweb.api.service.permission.IAuthenticationService
|
||||||
import top.fatweb.api.util.JwtUtil
|
import top.fatweb.api.util.JwtUtil
|
||||||
@@ -56,10 +57,11 @@ class AuthenticationServiceImpl(
|
|||||||
return LoginVo(jwt, loginUser.user.lastLoginTime, loginUser.user.lastLoginIp)
|
return LoginVo(jwt, loginUser.user.lastLoginTime, loginUser.user.lastLoginIp)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout(token: String): Boolean =
|
override fun logout(token: String): Boolean = redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token)
|
||||||
redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token)
|
|
||||||
|
|
||||||
override fun renewToken(token: String): TokenVo {
|
override fun renewToken(token: String): TokenVo {
|
||||||
|
val loginUser = WebUtil.getLoginUser() ?: let { throw TokenHasExpiredException() }
|
||||||
|
|
||||||
val oldRedisKey = "${SecurityConstants.jwtIssuer}_login:" + token
|
val oldRedisKey = "${SecurityConstants.jwtIssuer}_login:" + token
|
||||||
redisUtil.delObject(oldRedisKey)
|
redisUtil.delObject(oldRedisKey)
|
||||||
val jwt = JwtUtil.createJwt(WebUtil.getLoginUserId().toString())
|
val jwt = JwtUtil.createJwt(WebUtil.getLoginUserId().toString())
|
||||||
@@ -70,10 +72,7 @@ class AuthenticationServiceImpl(
|
|||||||
|
|
||||||
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt
|
val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt
|
||||||
redisUtil.setObject(
|
redisUtil.setObject(
|
||||||
redisKey,
|
redisKey, loginUser, SecurityConstants.redisTtl, SecurityConstants.redisTtlUnit
|
||||||
WebUtil.getLoginUser(),
|
|
||||||
SecurityConstants.redisTtl,
|
|
||||||
SecurityConstants.redisTtlUnit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return TokenVo(jwt)
|
return TokenVo(jwt)
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import top.fatweb.api.constant.SecurityConstants
|
|||||||
import top.fatweb.api.entity.permission.LoginUser
|
import top.fatweb.api.entity.permission.LoginUser
|
||||||
|
|
||||||
object WebUtil {
|
object WebUtil {
|
||||||
fun getLoginUser() = SecurityContextHolder.getContext().authentication.principal as LoginUser
|
fun getLoginUser() = if (SecurityContextHolder.getContext().authentication.principal is String) null
|
||||||
|
else SecurityContextHolder.getContext().authentication.principal as LoginUser
|
||||||
|
|
||||||
fun getLoginUserId() = getLoginUser().user.id
|
fun getLoginUserId() = getLoginUser()?.user?.id
|
||||||
|
|
||||||
fun getToken(tokenWithPrefix: String) = tokenWithPrefix.removePrefix(SecurityConstants.tokenPrefix)
|
fun getToken(tokenWithPrefix: String) = tokenWithPrefix.removePrefix(SecurityConstants.tokenPrefix)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user