mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 00:57:13 +08:00 
			
		
		
		
	新增:新增系统监控/登录日志功能,优化日志表结构,并新增记录错误信息(非未知异常不记录异常详情,只记录错误信息)
This commit is contained in:
		| @@ -41,7 +41,6 @@ import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.exception.BadRequestException; | ||||
| import top.charles7c.cnadmin.common.exception.ServiceException; | ||||
| import top.charles7c.cnadmin.common.model.dto.LogContext; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.StreamUtils; | ||||
| @@ -63,8 +62,8 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(Exception.class) | ||||
|     public R handleException(Exception e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生未知异常", request.getRequestURI(), e); | ||||
|         LogContextHolder.setException(e); | ||||
|         return R.fail(e.getMessage()); | ||||
|     } | ||||
|  | ||||
| @@ -74,8 +73,8 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(RuntimeException.class) | ||||
|     public R handleRuntimeException(RuntimeException e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生系统异常", request.getRequestURI(), e); | ||||
|         LogContextHolder.setException(e); | ||||
|         return R.fail(e.getMessage()); | ||||
|     } | ||||
|  | ||||
| @@ -85,8 +84,8 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) | ||||
|     @ExceptionHandler(ServiceException.class) | ||||
|     public R handleServiceException(ServiceException e, HttpServletRequest request) { | ||||
|         this.setException(e); | ||||
|         log.error("请求地址'{}',发生业务异常", request.getRequestURI(), e); | ||||
|         LogContextHolder.setErrorMsg(e.getMessage()); | ||||
|         return R.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); | ||||
|     } | ||||
|  | ||||
| @@ -97,6 +96,7 @@ public class GlobalExceptionHandler { | ||||
|     @ExceptionHandler(BadRequestException.class) | ||||
|     public R handleBadRequestException(BadRequestException e, HttpServletRequest request) { | ||||
|         log.error("请求地址'{}',自定义验证失败", request.getRequestURI(), e); | ||||
|         LogContextHolder.setErrorMsg(e.getMessage()); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage()); | ||||
|     } | ||||
|  | ||||
| @@ -107,8 +107,9 @@ public class GlobalExceptionHandler { | ||||
|     @ExceptionHandler(BindException.class) | ||||
|     public R handleBindException(BindException e, HttpServletRequest request) { | ||||
|         log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e); | ||||
|         String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ","); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), message); | ||||
|         String errorMsg = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ","); | ||||
|         LogContextHolder.setErrorMsg(errorMsg); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -118,8 +119,9 @@ public class GlobalExceptionHandler { | ||||
|     @ExceptionHandler(ConstraintViolationException.class) | ||||
|     public R constraintViolationException(ConstraintViolationException e, HttpServletRequest request) { | ||||
|         log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e); | ||||
|         String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ","); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), message); | ||||
|         String errorMsg = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ","); | ||||
|         LogContextHolder.setErrorMsg(errorMsg); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -129,8 +131,10 @@ public class GlobalExceptionHandler { | ||||
|     @ExceptionHandler(MethodArgumentNotValidException.class) | ||||
|     public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { | ||||
|         log.error("请求地址'{}',参数验证失败", request.getRequestURI(), e); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), ExceptionUtils | ||||
|             .exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage())); | ||||
|         String errorMsg = ExceptionUtils | ||||
|             .exToNull(() -> Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()); | ||||
|         LogContextHolder.setErrorMsg(errorMsg); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -143,6 +147,7 @@ public class GlobalExceptionHandler { | ||||
|         String subMsg = StrUtil.format("参数名:'{}',期望参数类型:'{}'", e.getName(), e.getParameter().getParameterType()); | ||||
|         log.error("请求地址'{}',参数转换失败。方法:'{}',{}", request.getRequestURI(), | ||||
|             Objects.requireNonNull(e.getParameter().getMethod()).getName(), subMsg, e); | ||||
|         LogContextHolder.setErrorMsg(subMsg); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), subMsg); | ||||
|     } | ||||
|  | ||||
| @@ -152,6 +157,7 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) | ||||
|     @ExceptionHandler(HttpRequestMethodNotSupportedException.class) | ||||
|     public R handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { | ||||
|         LogContextHolder.setErrorMsg(e.getMessage()); | ||||
|         log.error("请求地址'{}',不支持'{}'请求", request.getRequestURI(), e.getMethod()); | ||||
|         return R.fail(HttpStatus.METHOD_NOT_ALLOWED.value(), e.getMessage()); | ||||
|     } | ||||
| @@ -163,7 +169,9 @@ public class GlobalExceptionHandler { | ||||
|     @ExceptionHandler(NotLoginException.class) | ||||
|     public R handleNotLoginException(NotLoginException e, HttpServletRequest request) { | ||||
|         log.error("请求地址'{}',认证失败,无法访问系统资源", request.getRequestURI(), e); | ||||
|         return R.fail(HttpStatus.UNAUTHORIZED.value(), "登录状态已过期,请重新登录"); | ||||
|         String errorMsg = "登录状态已过期,请重新登录"; | ||||
|         LogContextHolder.setErrorMsg(errorMsg); | ||||
|         return R.fail(HttpStatus.UNAUTHORIZED.value(), errorMsg); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -172,22 +180,10 @@ public class GlobalExceptionHandler { | ||||
|     @ResponseStatus(HttpStatus.BAD_REQUEST) | ||||
|     @ExceptionHandler(MaxUploadSizeExceededException.class) | ||||
|     public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) { | ||||
|         String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for"); | ||||
|         log.error("请求地址'{}',上传文件失败,文件大小超过限制", request.getRequestURI(), e); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), | ||||
|             String.format("请上传小于 %s MB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在系统日志上下文中保存异常信息 | ||||
|      * | ||||
|      * @param e | ||||
|      *            异常信息 | ||||
|      */ | ||||
|     private void setException(Exception e) { | ||||
|         LogContext logContext = LogContextHolder.get(); | ||||
|         if (logContext != null) { | ||||
|             logContext.setException(e); | ||||
|         } | ||||
|         String sizeLimit = StrUtil.subBetween(e.getMessage(), "The maximum size ", " for"); | ||||
|         String errorMsg = String.format("请上传小于 %s MB 的文件", NumberUtil.parseLong(sizeLimit) / 1024 / 1024); | ||||
|         LogContextHolder.setErrorMsg(errorMsg); | ||||
|         return R.fail(HttpStatus.BAD_REQUEST.value(), errorMsg); | ||||
|     } | ||||
| } | ||||
| @@ -40,7 +40,12 @@ public class LogContext { | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     /** | ||||
|      * 异常 | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     private String errorMsg; | ||||
|  | ||||
|     /** | ||||
|      * 异常信息 | ||||
|      */ | ||||
|     private Exception exception; | ||||
| } | ||||
|   | ||||
| @@ -57,4 +57,31 @@ public class LogContextHolder { | ||||
|     public static void remove() { | ||||
|         LOG_THREAD_LOCAL.remove(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在系统日志上下文中保存异常信息 | ||||
|      * | ||||
|      * @param e | ||||
|      *            异常信息 | ||||
|      */ | ||||
|     public static void setException(Exception e) { | ||||
|         LogContext logContext = get(); | ||||
|         if (logContext != null) { | ||||
|             logContext.setErrorMsg(e.getMessage()); | ||||
|             logContext.setException(e); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在系统日志上下文中保存错误信息(非未知异常不记录异常信息,只记录错误信息) | ||||
|      * | ||||
|      * @param errorMsg | ||||
|      *            错误信息 | ||||
|      */ | ||||
|     public static void setErrorMsg(String errorMsg) { | ||||
|         LogContext logContext = get(); | ||||
|         if (logContext != null) { | ||||
|             logContext.setErrorMsg(errorMsg); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -110,7 +110,7 @@ public class LogInterceptor implements HandlerInterceptor { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录请求耗时及异常信息 | ||||
|      * 记录请求耗时及异常详情 | ||||
|      * | ||||
|      * @return 系统日志信息 | ||||
|      */ | ||||
| @@ -123,11 +123,17 @@ public class LogInterceptor implements HandlerInterceptor { | ||||
|             sysLog.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(sysLog.getCreateTime())); | ||||
|             sysLog.setStatus(LogStatusEnum.SUCCESS); | ||||
|  | ||||
|             // 记录异常信息 | ||||
|             // 记录错误信息(非未知异常不记录异常详情,只记录错误信息) | ||||
|             String errorMsg = logContext.getErrorMsg(); | ||||
|             if (StrUtil.isNotBlank(errorMsg)) { | ||||
|                 sysLog.setStatus(LogStatusEnum.FAILURE); | ||||
|                 sysLog.setErrorMsg(errorMsg); | ||||
|             } | ||||
|             // 记录异常详情 | ||||
|             Exception exception = logContext.getException(); | ||||
|             if (exception != null) { | ||||
|                 sysLog.setStatus(LogStatusEnum.FAILURE); | ||||
|                 sysLog.setException(ExceptionUtil.stacktraceToString(exception, -1)); | ||||
|                 sysLog.setExceptionDetail(ExceptionUtil.stacktraceToString(exception, -1)); | ||||
|             } | ||||
|             return sysLog; | ||||
|         } | ||||
|   | ||||
| @@ -110,9 +110,14 @@ public class SysLog implements Serializable { | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 异常 | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     private String exception; | ||||
|     private String errorMsg; | ||||
|  | ||||
|     /** | ||||
|      * 异常详情 | ||||
|      */ | ||||
|     private String exceptionDetail; | ||||
|  | ||||
|     /** | ||||
|      * 创建人 | ||||
|   | ||||
| @@ -0,0 +1,59 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.model.query; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import org.springdoc.api.annotations.ParameterObject; | ||||
| import org.springframework.format.annotation.DateTimeFormat; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.annotation.Query; | ||||
|  | ||||
| /** | ||||
|  * 登录日志查询条件 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/1/16 23:25 | ||||
|  */ | ||||
| @Data | ||||
| @ParameterObject | ||||
| @Schema(description = "登录日志查询条件") | ||||
| public class LoginLogQuery implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 登录状态(1成功 2失败) | ||||
|      */ | ||||
|     @Schema(description = "登录状态(1成功 2失败)") | ||||
|     @Query(type = Query.Type.EQUAL) | ||||
|     private Integer status; | ||||
|  | ||||
|     /** | ||||
|      * 登录时间 | ||||
|      */ | ||||
|     @Schema(description = "登录时间") | ||||
|     @Query(type = Query.Type.BETWEEN) | ||||
|     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") | ||||
|     private List<Date> createTime; | ||||
| } | ||||
| @@ -14,29 +14,32 @@ | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package top.charles7c.cnadmin.monitor.service; | ||||
| package top.charles7c.cnadmin.monitor.model.vo; | ||||
| 
 | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||
| import lombok.Data; | ||||
| 
 | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
| 
 | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| 
 | ||||
| /** | ||||
|  * 操作日志业务接口 | ||||
|  * 基础日志信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/1/15 21:05 | ||||
|  * @since 2023/1/17 21:43 | ||||
|  */ | ||||
| public interface OperationLogService { | ||||
| @Data | ||||
| public class LogVO { | ||||
| 
 | ||||
|     /** | ||||
|      * 分页查询列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 分页信息 | ||||
|      * 创建人 | ||||
|      */ | ||||
|     PageInfo<OperationLogVO> list(OperationLogQuery query, PageQuery pageQuery); | ||||
|     @JsonIgnore | ||||
|     private Long createUser; | ||||
| 
 | ||||
|     /** | ||||
|      * 创建人 | ||||
|      */ | ||||
|     @Schema(description = "创建人") | ||||
|     private String createUserString; | ||||
| } | ||||
| @@ -0,0 +1,87 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.model.vo; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.time.LocalDateTime; | ||||
|  | ||||
| import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.enums.LogStatusEnum; | ||||
|  | ||||
| /** | ||||
|  * 登录日志信息 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/1/16 23:19 | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "登录日志信息") | ||||
| public class LoginLogVO extends LogVO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 日志ID | ||||
|      */ | ||||
|     @Schema(description = "日志ID") | ||||
|     private Long logId; | ||||
|  | ||||
|     /** | ||||
|      * 日志描述 | ||||
|      */ | ||||
|     @Schema(description = "日志描述") | ||||
|     private String description; | ||||
|  | ||||
|     /** | ||||
|      * 操作状态(1成功 2失败) | ||||
|      */ | ||||
|     @Schema(description = "操作状态(1成功 2失败)", type = "Integer", allowableValues = {"1", "2"}) | ||||
|     private LogStatusEnum status; | ||||
|  | ||||
|     /** | ||||
|      * 登录IP | ||||
|      */ | ||||
|     @Schema(description = "登录IP") | ||||
|     private String clientIp; | ||||
|  | ||||
|     /** | ||||
|      * 登录地点 | ||||
|      */ | ||||
|     @Schema(description = "登录地点") | ||||
|     private String location; | ||||
|  | ||||
|     /** | ||||
|      * 浏览器 | ||||
|      */ | ||||
|     @Schema(description = "浏览器") | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     @Schema(description = "错误信息") | ||||
|     private String errorMsg; | ||||
|  | ||||
|     /** | ||||
|      * 登录时间 | ||||
|      */ | ||||
|     @Schema(description = "登录时间") | ||||
|     private LocalDateTime createTime; | ||||
| } | ||||
| @@ -23,8 +23,6 @@ import lombok.Data; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.media.Schema; | ||||
|  | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
|  | ||||
| import top.charles7c.cnadmin.monitor.enums.LogStatusEnum; | ||||
|  | ||||
| /** | ||||
| @@ -35,7 +33,7 @@ import top.charles7c.cnadmin.monitor.enums.LogStatusEnum; | ||||
|  */ | ||||
| @Data | ||||
| @Schema(description = "操作日志信息") | ||||
| public class OperationLogVO implements Serializable { | ||||
| public class OperationLogVO extends LogVO implements Serializable { | ||||
|  | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
| @@ -76,16 +74,10 @@ public class OperationLogVO implements Serializable { | ||||
|     private String browser; | ||||
|  | ||||
|     /** | ||||
|      * 操作人 | ||||
|      * 错误信息 | ||||
|      */ | ||||
|     @JsonIgnore | ||||
|     private Long createUser; | ||||
|  | ||||
|     /** | ||||
|      * 操作人 | ||||
|      */ | ||||
|     @Schema(description = "操作人") | ||||
|     private String createUserString; | ||||
|     @Schema(description = "错误信息") | ||||
|     private String errorMsg; | ||||
|  | ||||
|     /** | ||||
|      * 操作时间 | ||||
|   | ||||
| @@ -16,6 +16,13 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.service; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.monitor.model.query.LoginLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.LoginLogVO; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||
|  | ||||
| /** | ||||
|  * 系统日志业务接口 | ||||
|  * | ||||
| @@ -24,4 +31,25 @@ package top.charles7c.cnadmin.monitor.service; | ||||
|  */ | ||||
| public interface LogService { | ||||
|  | ||||
|     /** | ||||
|      * 分页查询操作日志列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 操作日志分页信息 | ||||
|      */ | ||||
|     PageInfo<OperationLogVO> list(OperationLogQuery query, PageQuery pageQuery); | ||||
|  | ||||
|     /** | ||||
|      * 分页查询登录日志列表 | ||||
|      * | ||||
|      * @param query | ||||
|      *            查询条件 | ||||
|      * @param pageQuery | ||||
|      *            分页查询条件 | ||||
|      * @return 登录日志分页信息 | ||||
|      */ | ||||
|     PageInfo<LoginLogVO> list(LoginLogQuery query, PageQuery pageQuery); | ||||
| } | ||||
|   | ||||
| @@ -16,6 +16,10 @@ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.service.impl; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| @@ -23,9 +27,25 @@ import org.springframework.context.event.EventListener; | ||||
| import org.springframework.scheduling.annotation.Async; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||
| import top.charles7c.cnadmin.common.util.ReflectUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| import top.charles7c.cnadmin.monitor.mapper.LogMapper; | ||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||
| import top.charles7c.cnadmin.monitor.model.query.LoginLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.LogVO; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.LoginLogVO; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||
| import top.charles7c.cnadmin.monitor.service.LogService; | ||||
| import top.charles7c.cnadmin.system.service.UserService; | ||||
|  | ||||
| /** | ||||
|  * 系统日志业务实现类 | ||||
| @@ -39,10 +59,69 @@ import top.charles7c.cnadmin.monitor.service.LogService; | ||||
| public class LogServiceImpl implements LogService { | ||||
|  | ||||
|     private final LogMapper logMapper; | ||||
|     private final UserService userService; | ||||
|  | ||||
|     @Async | ||||
|     @EventListener | ||||
|     public void save(SysLog sysLog) { | ||||
|         logMapper.insert(sysLog); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageInfo<OperationLogVO> list(OperationLogQuery query, PageQuery pageQuery) { | ||||
|         QueryWrapper<SysLog> queryWrapper = QueryHelper.build(query); | ||||
|  | ||||
|         // 限定查询信息 | ||||
|         String[] fieldsName = ReflectUtils.getNonStaticFieldsName(OperationLogVO.class); | ||||
|         List<String> columns = Arrays.stream(fieldsName).map(StrUtil::toUnderlineCase) | ||||
|             .filter(n -> !n.endsWith("string")).collect(Collectors.toList()); | ||||
|         queryWrapper.select(columns); | ||||
|  | ||||
|         // 分页查询 | ||||
|         IPage<SysLog> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper); | ||||
|         PageInfo<OperationLogVO> pageInfo = PageInfo.build(page, OperationLogVO.class); | ||||
|  | ||||
|         // 填充数据(如果是查询个人操作日志,只查询一次用户信息即可) | ||||
|         if (query.getUid() != null) { | ||||
|             String nickname = ExceptionUtils.exToNull(() -> userService.getById(query.getUid()).getNickname()); | ||||
|             pageInfo.getList().forEach(o -> o.setCreateUserString(nickname)); | ||||
|         } else { | ||||
|             pageInfo.getList().forEach(this::fill); | ||||
|         } | ||||
|         return pageInfo; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public PageInfo<LoginLogVO> list(LoginLogQuery query, PageQuery pageQuery) { | ||||
|         QueryWrapper<SysLog> queryWrapper = QueryHelper.build(query); | ||||
|         queryWrapper.and(qw -> qw.like("request_url", "/auth/login").or().like("request_url", "/auth/logout")); | ||||
|  | ||||
|         // 限定查询信息 | ||||
|         String[] fieldsName = ReflectUtils.getNonStaticFieldsName(LoginLogVO.class); | ||||
|         List<String> columns = Arrays.stream(fieldsName).map(StrUtil::toUnderlineCase) | ||||
|             .filter(n -> !n.endsWith("string")).collect(Collectors.toList()); | ||||
|         queryWrapper.select(columns); | ||||
|  | ||||
|         // 分页查询 | ||||
|         IPage<SysLog> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper); | ||||
|         PageInfo<LoginLogVO> pageInfo = PageInfo.build(page, LoginLogVO.class); | ||||
|  | ||||
|         // 填充数据 | ||||
|         pageInfo.getList().forEach(this::fill); | ||||
|         return pageInfo; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param vo | ||||
|      *            VO | ||||
|      */ | ||||
|     private void fill(LogVO vo) { | ||||
|         Long createUser = vo.getCreateUser(); | ||||
|         if (createUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         vo.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getById(vo.getCreateUser())).getNickname()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,97 +0,0 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.monitor.service.impl; | ||||
|  | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
|  | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.common.util.ReflectUtils; | ||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||
| import top.charles7c.cnadmin.monitor.mapper.LogMapper; | ||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||
| import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||
| import top.charles7c.cnadmin.monitor.service.OperationLogService; | ||||
| import top.charles7c.cnadmin.system.mapper.UserMapper; | ||||
| import top.charles7c.cnadmin.system.model.entity.SysUser; | ||||
|  | ||||
| /** | ||||
|  * 操作日志业务实现类 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/1/15 21:05 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Service | ||||
| @RequiredArgsConstructor | ||||
| public class OperationLogServiceImpl implements OperationLogService { | ||||
|  | ||||
|     private final LogMapper logMapper; | ||||
|     private final UserMapper userMapper; | ||||
|  | ||||
|     @Override | ||||
|     public PageInfo<OperationLogVO> list(OperationLogQuery query, PageQuery pageQuery) { | ||||
|         QueryWrapper<SysLog> queryWrapper = QueryHelper.build(query); | ||||
|  | ||||
|         // 限定查询信息 | ||||
|         String[] fieldsName = ReflectUtils.getNonStaticFieldsName(OperationLogVO.class); | ||||
|         List<String> columns = Arrays.stream(fieldsName).map(StrUtil::toUnderlineCase) | ||||
|             .filter(n -> !n.endsWith("string")).collect(Collectors.toList()); | ||||
|         queryWrapper.select(columns); | ||||
|  | ||||
|         // 分页查询 | ||||
|         IPage<SysLog> page = logMapper.selectPage(pageQuery.toPage(), queryWrapper); | ||||
|         PageInfo<OperationLogVO> pageInfo = PageInfo.build(page, OperationLogVO.class); | ||||
|  | ||||
|         // 填充数据(如果是查询个人操作日志,只查询一次用户信息即可) | ||||
|         if (query.getUid() != null) { | ||||
|             SysUser sysUser = userMapper.selectById(query.getUid()); | ||||
|             pageInfo.getList().forEach(o -> o.setCreateUserString(sysUser.getUsername())); | ||||
|         } else { | ||||
|             pageInfo.getList().forEach(this::fill); | ||||
|         } | ||||
|         return pageInfo; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 填充数据 | ||||
|      * | ||||
|      * @param vo | ||||
|      *            VO | ||||
|      */ | ||||
|     private void fill(OperationLogVO vo) { | ||||
|         Long createUser = vo.getCreateUser(); | ||||
|         if (createUser == null) { | ||||
|             return; | ||||
|         } | ||||
|         SysUser sysUser = userMapper.selectById(createUser); | ||||
|         vo.setCreateUserString(sysUser.getUsername()); | ||||
|     } | ||||
| } | ||||
| @@ -79,4 +79,13 @@ public interface UserService { | ||||
|      *            用户ID | ||||
|      */ | ||||
|     void updateEmail(String newEmail, String currentPassword, Long userId); | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户ID | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     SysUser getById(Long userId); | ||||
| } | ||||
|   | ||||
| @@ -141,14 +141,8 @@ public class UserServiceImpl implements UserService { | ||||
|         LoginHelper.updateLoginUser(loginUser); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 ID 查询 | ||||
|      * | ||||
|      * @param userId | ||||
|      *            用户 ID | ||||
|      * @return 用户信息 | ||||
|      */ | ||||
|     private SysUser getById(Long userId) { | ||||
|     @Override | ||||
|     public SysUser getById(Long userId) { | ||||
|         ValidationUtils.exIfNull(userId, "用户不存在"); | ||||
|         SysUser sysUser = userMapper.selectById(userId); | ||||
|         ValidationUtils.exIfNull(sysUser, String.format("ID为 [%s] 的用户已不存在", userId)); | ||||
|   | ||||
							
								
								
									
										33
									
								
								continew-admin-ui/src/api/monitor/login-log.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								continew-admin-ui/src/api/monitor/login-log.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import axios from 'axios'; | ||||
| import qs from 'query-string'; | ||||
|  | ||||
| export interface LoginLogRecord { | ||||
|   logId: string; | ||||
|   status: number; | ||||
|   clientIp: string; | ||||
|   location: string; | ||||
|   browser: string; | ||||
|   errorMsg: string; | ||||
|   createUserString: string; | ||||
|   createTime: string; | ||||
| } | ||||
|  | ||||
| export interface LoginLogParams extends Partial<LoginLogRecord> { | ||||
|   page: number; | ||||
|   size: number; | ||||
|   sort: Array<string>; | ||||
| } | ||||
|  | ||||
| export interface LoginLogListRes { | ||||
|   list: LoginLogRecord[]; | ||||
|   total: number; | ||||
| } | ||||
|  | ||||
| export function queryLoginLogList(params: LoginLogParams) { | ||||
|   return axios.get<LoginLogListRes>('/monitor/log/login', { | ||||
|     params, | ||||
|     paramsSerializer: (obj) => { | ||||
|       return qs.stringify(obj); | ||||
|     }, | ||||
|   }); | ||||
| } | ||||
| @@ -4,10 +4,11 @@ import qs from 'query-string'; | ||||
| export interface OperationLogRecord { | ||||
|   logId: string; | ||||
|   description: string; | ||||
|   status: number, | ||||
|   clientIp: string, | ||||
|   location: string, | ||||
|   browser: string, | ||||
|   status: number; | ||||
|   clientIp: string; | ||||
|   location: string; | ||||
|   browser: string; | ||||
|   errorMsg: string; | ||||
|   createUserString: string; | ||||
|   createTime: string; | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/en-US'; | ||||
| import localeDataAnalysis from '@/views/visualization/data-analysis/locale/en-US'; | ||||
| import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/en-US'; | ||||
|  | ||||
| import localeLog from '@/views/monitor/log/operation/locale/en-US'; | ||||
| import localeOperationLog from '@/views/monitor/log/operation/locale/en-US'; | ||||
| import localeLoginLog from '@/views/monitor/log/login/locale/en-US'; | ||||
|  | ||||
| import localeSearchTable from '@/views/list/search-table/locale/en-US'; | ||||
| import localeCardList from '@/views/list/card/locale/en-US'; | ||||
| @@ -55,7 +56,8 @@ export default { | ||||
|   ...localeDataAnalysis, | ||||
|   ...localeMultiDAnalysis, | ||||
|  | ||||
|   ...localeLog, | ||||
|   ...localeOperationLog, | ||||
|   ...localeLoginLog, | ||||
|  | ||||
|   ...localeSearchTable, | ||||
|   ...localeCardList, | ||||
|   | ||||
| @@ -8,7 +8,8 @@ import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN'; | ||||
| import localeDataAnalysis from '@/views/visualization/data-analysis/locale/zh-CN'; | ||||
| import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/zh-CN'; | ||||
|  | ||||
| import localeLog from '@/views/monitor/log/operation/locale/zh-CN'; | ||||
| import localeOperationLog from '@/views/monitor/log/operation/locale/zh-CN'; | ||||
| import localeLoginLog from '@/views/monitor/log/login/locale/zh-CN'; | ||||
|  | ||||
| import localeSearchTable from '@/views/list/search-table/locale/zh-CN'; | ||||
| import localeCardList from '@/views/list/card/locale/zh-CN'; | ||||
| @@ -55,7 +56,8 @@ export default { | ||||
|   ...localeDataAnalysis, | ||||
|   ...localeMultiDAnalysis, | ||||
|  | ||||
|   ...localeLog, | ||||
|   ...localeOperationLog, | ||||
|   ...localeLoginLog, | ||||
|  | ||||
|   ...localeSearchTable, | ||||
|   ...localeCardList, | ||||
|   | ||||
| @@ -22,6 +22,16 @@ const Monitor: AppRouteRecordRaw = { | ||||
|         roles: ['*'], | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       path: 'log/login', | ||||
|       name: 'LoginLog', | ||||
|       component: () => import('@/views/monitor/log/login/index.vue'), | ||||
|       meta: { | ||||
|         locale: 'menu.log.login.list', | ||||
|         requiresAuth: true, | ||||
|         roles: ['*'], | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ import { DEFAULT_LAYOUT } from '../base'; | ||||
| import { AppRouteRecordRaw } from '../types'; | ||||
|  | ||||
| const USER: AppRouteRecordRaw = { | ||||
|   path: '/system/user', | ||||
|   path: '/login/user', | ||||
|   name: 'user', | ||||
|   component: DEFAULT_LAYOUT, | ||||
|   meta: { | ||||
|   | ||||
							
								
								
									
										214
									
								
								continew-admin-ui/src/views/monitor/log/login/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								continew-admin-ui/src/views/monitor/log/login/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,214 @@ | ||||
| <template> | ||||
|   <div class="container"> | ||||
|     <Breadcrumb :items="['menu.monitor', 'menu.log.login.list']" /> | ||||
|     <a-card class="general-card" :title="$t('menu.log.login.list')"> | ||||
|       <a-row style="margin-bottom: 15px"> | ||||
|         <a-col :span="24"> | ||||
|           <a-form | ||||
|             ref="queryFormRef" | ||||
|             :model="queryFormData" | ||||
|             layout="inline" | ||||
|           > | ||||
|             <a-form-item | ||||
|               field="status" | ||||
|               hide-label | ||||
|             > | ||||
|               <a-select | ||||
|                 v-model="queryFormData.status" | ||||
|                 :options="statusOptions" | ||||
|                 placeholder="登录状态搜索" | ||||
|                 allow-clear | ||||
|                 style="width: 150px;" | ||||
|               /> | ||||
|             </a-form-item> | ||||
|             <a-form-item | ||||
|               field="createTime" | ||||
|               hide-label | ||||
|             > | ||||
|               <a-range-picker | ||||
|                 v-model="queryFormData.createTime" | ||||
|                 format="YYYY-MM-DD HH:mm:ss" | ||||
|                 show-time | ||||
|                 style="width: 100%" | ||||
|               /> | ||||
|             </a-form-item> | ||||
|             <a-button type="primary" @click="toQuery"> | ||||
|               <template #icon> | ||||
|                 <icon-search /> | ||||
|               </template> | ||||
|               查询 | ||||
|             </a-button> | ||||
|             <a-button @click="resetQuery"> | ||||
|               <template #icon> | ||||
|                 <icon-refresh /> | ||||
|               </template> | ||||
|               重置 | ||||
|             </a-button> | ||||
|           </a-form> | ||||
|         </a-col> | ||||
|       </a-row> | ||||
|       <a-table | ||||
|         row-key="logId" | ||||
|         :loading="loading" | ||||
|         :pagination="pagination" | ||||
|         :columns="columns" | ||||
|         :data="renderData" | ||||
|         :bordered="false" | ||||
|         :stripe="true" | ||||
|         size="large" | ||||
|         @page-change="onPageChange" | ||||
|       > | ||||
|         <template #index="{ rowIndex }"> | ||||
|           {{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }} | ||||
|         </template> | ||||
|         <template #status="{ record }"> | ||||
|           <a-space v-if="record.status === 1"> | ||||
|             <a-tag color="green"> | ||||
|               <span class="circle pass"></span> | ||||
|               成功 | ||||
|             </a-tag> | ||||
|           </a-space> | ||||
|           <a-space v-else> | ||||
|             <a-tooltip :content="record.errorMsg"> | ||||
|               <a-tag color="red" style="cursor: pointer"> | ||||
|                 <span class="circle fail"></span> | ||||
|                 失败 | ||||
|               </a-tag> | ||||
|             </a-tooltip> | ||||
|           </a-space> | ||||
|         </template> | ||||
|         <template #operations> | ||||
|           <a-button v-permission="['admin']" type="text" size="small">详情</a-button> | ||||
|         </template> | ||||
|       </a-table> | ||||
|     </a-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts" setup> | ||||
|   import { computed, ref, reactive } from 'vue'; | ||||
|   import useLoading from '@/hooks/loading'; | ||||
|   import { queryLoginLogList, LoginLogRecord, LoginLogParams } from '@/api/monitor/login-log'; | ||||
|   import { Pagination } from '@/types/global'; | ||||
|   import type { SelectOptionData } from '@arco-design/web-vue/es/select/interface'; | ||||
|   import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'; | ||||
|   import { FormInstance } from '@arco-design/web-vue/es/form'; | ||||
|  | ||||
|   const { loading, setLoading } = useLoading(true); | ||||
|   const queryFormRef = ref<FormInstance>(); | ||||
|   const renderData = ref<LoginLogRecord[]>([]); | ||||
|  | ||||
|   const queryFormData = ref({ | ||||
|     status: undefined, | ||||
|     createTime: [], | ||||
|   }); | ||||
|   const statusOptions = computed<SelectOptionData[]>(() => [ | ||||
|     { | ||||
|       label: '成功', | ||||
|       value: 1, | ||||
|     }, | ||||
|     { | ||||
|       label: '失败', | ||||
|       value: 2, | ||||
|     }, | ||||
|   ]); | ||||
|  | ||||
|   const basePagination: Pagination = { | ||||
|     current: 1, | ||||
|     pageSize: 10, | ||||
|   }; | ||||
|   const pagination = reactive({ | ||||
|     ...basePagination, | ||||
|   }); | ||||
|   const columns = computed<TableColumnData[]>(() => [ | ||||
|     { | ||||
|       title: '序号', | ||||
|       dataIndex: 'index', | ||||
|       slotName: 'index', | ||||
|     }, | ||||
|     { | ||||
|       title: '用户昵称', | ||||
|       dataIndex: 'createUserString', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录行为', | ||||
|       dataIndex: 'description', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录状态', | ||||
|       dataIndex: 'status', | ||||
|       slotName: 'status', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录IP', | ||||
|       dataIndex: 'clientIp', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录地点', | ||||
|       dataIndex: 'location', | ||||
|     }, | ||||
|     { | ||||
|       title: '浏览器', | ||||
|       dataIndex: 'browser', | ||||
|     }, | ||||
|     { | ||||
|       title: '登录时间', | ||||
|       dataIndex: 'createTime', | ||||
|     }, | ||||
|   ]); | ||||
|   const fetchData = async ( | ||||
|     params: LoginLogParams = { page: 1, size: 10, sort: ['createTime,desc'] } | ||||
|   ) => { | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       const { data } = await queryLoginLogList(params); | ||||
|       renderData.value = data.list; | ||||
|       pagination.current = params.page; | ||||
|       pagination.total = data.total; | ||||
|     } catch (err) { | ||||
|       // you can report use errorHandler or other | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const onPageChange = (current: number) => { | ||||
|     fetchData({ page: current, size: pagination.pageSize, sort: ['createTime,desc'] }); | ||||
|   }; | ||||
|  | ||||
|   // 查询 | ||||
|   const toQuery = () => { | ||||
|     fetchData({ | ||||
|       page: pagination.current, | ||||
|       size: pagination.pageSize, | ||||
|       sort: ['createTime,desc'], | ||||
|       ...queryFormData.value, | ||||
|     } as unknown as LoginLogParams); | ||||
|   }; | ||||
|  | ||||
|   // 重置 | ||||
|   const resetQuery = async () => { | ||||
|     await queryFormRef.value?.resetFields(); | ||||
|     await fetchData(); | ||||
|   }; | ||||
|   fetchData(); | ||||
| </script> | ||||
|  | ||||
| <script lang="ts"> | ||||
|   export default { | ||||
|     name: 'LoginLog', | ||||
|   }; | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="less"> | ||||
|   .container { | ||||
|     padding: 0 20px 20px 20px; | ||||
|   } | ||||
|   :deep(.arco-table-th) { | ||||
|     &:last-child { | ||||
|       .arco-table-th-item-title { | ||||
|         margin-left: 16px; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| </style> | ||||
| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.log.login.list': 'Login log', | ||||
| }; | ||||
| @@ -0,0 +1,3 @@ | ||||
| export default { | ||||
|   'menu.log.login.list': '登录日志', | ||||
| }; | ||||
| @@ -81,10 +81,12 @@ | ||||
|             </a-tag> | ||||
|           </a-space> | ||||
|           <a-space v-else> | ||||
|             <a-tag color="red"> | ||||
|             <a-tooltip :content="record.errorMsg"> | ||||
|               <a-tag color="red" style="cursor: pointer"> | ||||
|                 <span class="circle fail"></span> | ||||
|                 失败 | ||||
|               </a-tag> | ||||
|             </a-tooltip> | ||||
|           </a-space> | ||||
|         </template> | ||||
|       </a-table> | ||||
|   | ||||
| @@ -22,10 +22,12 @@ | ||||
|           </a-tag> | ||||
|         </a-space> | ||||
|         <a-space v-else> | ||||
|           <a-tag color="red"> | ||||
|           <a-tooltip :content="record.errorMsg"> | ||||
|             <a-tag color="red" style="cursor: pointer"> | ||||
|               <span class="circle fail"></span> | ||||
|               失败 | ||||
|             </a-tag> | ||||
|           </a-tooltip> | ||||
|         </a-space> | ||||
|       </template> | ||||
|       <template #pagination-left> | ||||
|   | ||||
| @@ -0,0 +1,58 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.cnadmin.webapi.controller.monitor; | ||||
|  | ||||
| import lombok.RequiredArgsConstructor; | ||||
|  | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
|  | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
|  | ||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||
| import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.monitor.model.query.LoginLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.LoginLogVO; | ||||
| import top.charles7c.cnadmin.monitor.service.LogService; | ||||
|  | ||||
| /** | ||||
|  * 登录日志 API | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 2023/1/16 23:17 | ||||
|  */ | ||||
| @Tag(name = "登录日志 API") | ||||
| @Validated | ||||
| @RestController | ||||
| @RequiredArgsConstructor | ||||
| @RequestMapping(value = "/monitor/log/login", produces = MediaType.APPLICATION_JSON_VALUE) | ||||
| public class LoginLogController { | ||||
|  | ||||
|     private final LogService logService; | ||||
|  | ||||
|     @Operation(summary = "分页查询登录日志列表") | ||||
|     @GetMapping | ||||
|     public R<PageInfo<LoginLogVO>> list(@Validated LoginLogQuery query, @Validated PageQuery pageQuery) { | ||||
|         PageInfo<LoginLogVO> pageInfo = logService.list(query, pageQuery); | ||||
|         return R.ok(pageInfo); | ||||
|     } | ||||
| } | ||||
| @@ -32,7 +32,7 @@ import top.charles7c.cnadmin.common.model.vo.PageInfo; | ||||
| import top.charles7c.cnadmin.common.model.vo.R; | ||||
| import top.charles7c.cnadmin.monitor.model.query.OperationLogQuery; | ||||
| import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||
| import top.charles7c.cnadmin.monitor.service.OperationLogService; | ||||
| import top.charles7c.cnadmin.monitor.service.LogService; | ||||
|  | ||||
| /** | ||||
|  * 操作日志 API | ||||
| @@ -47,12 +47,12 @@ import top.charles7c.cnadmin.monitor.service.OperationLogService; | ||||
| @RequestMapping(value = "/monitor/log/operation", produces = MediaType.APPLICATION_JSON_VALUE) | ||||
| public class OperationLogController { | ||||
|  | ||||
|     private final OperationLogService operationLogService; | ||||
|     private final LogService logService; | ||||
|  | ||||
|     @Operation(summary = "分页查询操作日志列表") | ||||
|     @GetMapping | ||||
|     public R<PageInfo<OperationLogVO>> list(@Validated OperationLogQuery query, @Validated PageQuery pageQuery) { | ||||
|         PageInfo<OperationLogVO> pageInfo = operationLogService.list(query, pageQuery); | ||||
|         PageInfo<OperationLogVO> pageInfo = logService.list(query, pageQuery); | ||||
|         return R.ok(pageInfo); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -39,7 +39,8 @@ CREATE TABLE IF NOT EXISTS `sys_log` ( | ||||
|     `client_ip` varchar(255) DEFAULT NULL COMMENT '客户端IP', | ||||
|     `location` varchar(512) DEFAULT NULL COMMENT 'IP归属地', | ||||
|     `browser` varchar(255) DEFAULT NULL COMMENT '浏览器', | ||||
|     `exception` mediumtext DEFAULT NULL COMMENT '异常', | ||||
|     `error_msg` text DEFAULT NULL COMMENT '错误信息', | ||||
|     `exception_detail` mediumtext DEFAULT NULL COMMENT '异常详情', | ||||
|     `create_user` bigint(20) unsigned DEFAULT NULL COMMENT '创建人', | ||||
|     `create_time` datetime NOT NULL COMMENT '创建时间', | ||||
|     PRIMARY KEY (`log_id`) USING BTREE, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user