From 1d77caf390474ae15dadf8e3c0cbaefab19ded5a Mon Sep 17 00:00:00 2001 From: FatttSnake Date: Thu, 19 Oct 2023 16:01:42 +0800 Subject: [PATCH] Finish system log --- db/schema.sql | 10 +- .../top/fatweb/api/config/SysLogConfig.kt | 15 +++ .../kotlin/top/fatweb/api/entity/SysLog.kt | 28 ++--- .../api/interceptor/SysLogInterceptor.kt | 110 ++++++++++++++++++ .../impl/AuthenticationServiceImpl.kt | 11 +- .../kotlin/top/fatweb/api/util/WebUtil.kt | 5 +- 6 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt create mode 100644 src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt diff --git a/db/schema.sql b/db/schema.sql index 73ff84c..70cc8af 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -28,7 +28,7 @@ create table t_sys_log id bigint not null, log_type varchar(50) 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_method varchar(10) default null comment '请求方式', request_params text comment '请求参数', @@ -36,12 +36,10 @@ create table t_sys_log request_server_address varchar(50) not null comment '请求服务器地址', is_exception char(1) default null comment '是否异常', exception_info text comment '异常信息', - start_time datetime not null comment '开始时间', - end_time datetime not null comment '结束时间', - execute_time int default null comment '执行时间', + start_time datetime(3) not null comment '开始时间', + end_time datetime(3) not null comment '结束时间', + execute_time bigint 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, key idx_sys_log_log_type (log_type) using btree, key idx_sys_log_operate_user_id (operate_user_id) using btree, diff --git a/src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt b/src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt new file mode 100644 index 0000000..2288877 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/config/SysLogConfig.kt @@ -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") + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/entity/SysLog.kt b/src/main/kotlin/top/fatweb/api/entity/SysLog.kt index 2fc1047..0e49aa2 100644 --- a/src/main/kotlin/top/fatweb/api/entity/SysLog.kt +++ b/src/main/kotlin/top/fatweb/api/entity/SysLog.kt @@ -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.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.TableField +import com.baomidou.mybatisplus.annotation.TableId +import com.baomidou.mybatisplus.annotation.TableName +import java.io.Serializable +import java.time.LocalDateTime /** *

@@ -96,7 +96,7 @@ class SysLog : Serializable { * 执行时间 */ @TableField("execute_time") - var executeTime: Int? = null + var executeTime: Long? = null /** * 用户代理 @@ -104,19 +104,7 @@ class SysLog : Serializable { @TableField("user_agent") var userAgent: String? = null - /** - * 操作系统 - */ - @TableField("device_name") - var deviceName: String? = null - - /** - * 浏览器名称 - */ - @TableField("browser_name") - var browserName: String? = null - 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)" } } diff --git a/src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt b/src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt new file mode 100644 index 0000000..860c565 --- /dev/null +++ b/src/main/kotlin/top/fatweb/api/interceptor/SysLogInterceptor.kt @@ -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 { + private val logger: Logger = LoggerFactory.getLogger(this::class.java) + private val sysLogThreadLocal = ThreadLocal() + private val resultThreadLocal = ThreadLocal() + + 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 { + 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>): Boolean = + true + + override fun beforeBodyWrite( + body: Any?, + returnType: MethodParameter, + selectedContentType: MediaType, + selectedConverterType: Class>, + request: ServerHttpRequest, + response: ServerHttpResponse + ): Any? { + resultThreadLocal.set(body) + return body + } +} \ No newline at end of file diff --git a/src/main/kotlin/top/fatweb/api/service/permission/impl/AuthenticationServiceImpl.kt b/src/main/kotlin/top/fatweb/api/service/permission/impl/AuthenticationServiceImpl.kt index cc669c0..139ab2b 100644 --- a/src/main/kotlin/top/fatweb/api/service/permission/impl/AuthenticationServiceImpl.kt +++ b/src/main/kotlin/top/fatweb/api/service/permission/impl/AuthenticationServiceImpl.kt @@ -10,6 +10,7 @@ import org.springframework.stereotype.Service import top.fatweb.api.constant.SecurityConstants import top.fatweb.api.entity.permission.LoginUser import top.fatweb.api.entity.permission.User +import top.fatweb.api.exception.TokenHasExpiredException import top.fatweb.api.service.IUserService import top.fatweb.api.service.permission.IAuthenticationService import top.fatweb.api.util.JwtUtil @@ -56,10 +57,11 @@ class AuthenticationServiceImpl( return LoginVo(jwt, loginUser.user.lastLoginTime, loginUser.user.lastLoginIp) } - override fun logout(token: String): Boolean = - redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token) + override fun logout(token: String): Boolean = redisUtil.delObject("${SecurityConstants.jwtIssuer}_login:" + token) override fun renewToken(token: String): TokenVo { + val loginUser = WebUtil.getLoginUser() ?: let { throw TokenHasExpiredException() } + val oldRedisKey = "${SecurityConstants.jwtIssuer}_login:" + token redisUtil.delObject(oldRedisKey) val jwt = JwtUtil.createJwt(WebUtil.getLoginUserId().toString()) @@ -70,10 +72,7 @@ class AuthenticationServiceImpl( val redisKey = "${SecurityConstants.jwtIssuer}_login:" + jwt redisUtil.setObject( - redisKey, - WebUtil.getLoginUser(), - SecurityConstants.redisTtl, - SecurityConstants.redisTtlUnit + redisKey, loginUser, SecurityConstants.redisTtl, SecurityConstants.redisTtlUnit ) return TokenVo(jwt) diff --git a/src/main/kotlin/top/fatweb/api/util/WebUtil.kt b/src/main/kotlin/top/fatweb/api/util/WebUtil.kt index da37681..ac280d8 100644 --- a/src/main/kotlin/top/fatweb/api/util/WebUtil.kt +++ b/src/main/kotlin/top/fatweb/api/util/WebUtil.kt @@ -6,9 +6,10 @@ import top.fatweb.api.constant.SecurityConstants import top.fatweb.api.entity.permission.LoginUser 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)