mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	新增:个人中心新增查询操作日志功能,优化日志表结构,并支持关闭记录内网 IP 操作
This commit is contained in:
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @@ -134,9 +134,10 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | |||||||
|   │      │      └─ cnadmin |   │      │      └─ cnadmin | ||||||
|   │      │        ├─ webapi |   │      │        ├─ webapi | ||||||
|   │      │        │  └─ controller   |   │      │        │  └─ controller   | ||||||
|   │      │        │    ├─ auth    # 认证相关 API |   │      │        │    ├─ auth     # 认证相关 API | ||||||
|   │      │        │    ├─ common  # 公共相关 API(例如:验证码 API 等) |   │      │        │    ├─ common   # 公共相关 API(例如:验证码 API 等) | ||||||
|   │      │        │    └─ system  # 系统管理相关 API |   │      │        │    ├─ monitor  # 系统监控相关 API | ||||||
|  |   │      │        │    └─ system   # 系统管理相关 API | ||||||
|   │      │        └─ ContinewAdminApplication.java  # 启动入口 |   │      │        └─ ContinewAdminApplication.java  # 启动入口 | ||||||
|   │      └─ resources   # 工程配置目录 |   │      └─ resources   # 工程配置目录 | ||||||
|   │        ├─ db.changelog   # 数据库脚本文件 |   │        ├─ db.changelog   # 数据库脚本文件 | ||||||
| @@ -159,7 +160,9 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | |||||||
|   │      │          ├─ interceptor   # 系统监控相关拦截器 |   │      │          ├─ interceptor   # 系统监控相关拦截器 | ||||||
|   │      │          ├─ mapper        # 系统监控相关 Mapper |   │      │          ├─ mapper        # 系统监控相关 Mapper | ||||||
|   │      │          ├─ model         # 系统监控相关模型 |   │      │          ├─ model         # 系统监控相关模型 | ||||||
|   │      │          │  └─ entity       # 系统监控相关实体对象 |   │      │          │  ├─ entity       # 系统监控相关实体对象 | ||||||
|  |   │      │          │  ├─ query        # 系统监控相关查询条件 | ||||||
|  |   │      │          │  └─ vo           # 系统监控相关 VO(View Object) | ||||||
|   │      │          └─ service       # 系统监控相关业务接口及实现类 |   │      │          └─ service       # 系统监控相关业务接口及实现类 | ||||||
|   │      │             └─ impl         # 系统监控相关业务实现类 |   │      │             └─ impl         # 系统监控相关业务实现类 | ||||||
|   │      └─ resources   # 工程配置目录 |   │      └─ resources   # 工程配置目录 | ||||||
| @@ -197,6 +200,7 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | |||||||
|   │          └─ charles7c |   │          └─ charles7c | ||||||
|   │            └─ cnadmin |   │            └─ cnadmin | ||||||
|   │              └─ common |   │              └─ common | ||||||
|  |   │                ├─ annotation   # 公共注解 | ||||||
|   │                ├─ config       # 公共配置 |   │                ├─ config       # 公共配置 | ||||||
|   │                │  ├─ jackson      # Jackson 配置 |   │                │  ├─ jackson      # Jackson 配置 | ||||||
|   │                │  ├─ mybatis      # MyBatis Plus 配置 |   │                │  ├─ mybatis      # MyBatis Plus 配置 | ||||||
| @@ -209,6 +213,7 @@ continew-admin  # 全局通用项目配置及依赖版本管理 | |||||||
|   │                ├─ model        # 公共模型 |   │                ├─ model        # 公共模型 | ||||||
|   │                │  ├─ dto          # 公共 DTO(Data Transfer Object) |   │                │  ├─ dto          # 公共 DTO(Data Transfer Object) | ||||||
|   │                │  ├─ entity       # 公共实体对象 |   │                │  ├─ entity       # 公共实体对象 | ||||||
|  |   │                │  ├─ query        # 公共查询条件 | ||||||
|   │                │  └─ vo           # 公共 VO(View Object) |   │                │  └─ vo           # 公共 VO(View Object) | ||||||
|   │                └─ util         # 公共工具类 |   │                └─ util         # 公共工具类 | ||||||
|   │                  ├─ helper        # 公共 Helper(助手) |   │                  ├─ helper        # 公共 Helper(助手) | ||||||
| @@ -226,6 +231,7 @@ continew-admin | |||||||
|     │  ├─ api               # 请求接口 |     │  ├─ api               # 请求接口 | ||||||
|     │  │  ├─ auth             # 认证模块 |     │  │  ├─ auth             # 认证模块 | ||||||
|     │  │  ├─ common           # 公共模块 |     │  │  ├─ common           # 公共模块 | ||||||
|  |     │  │  ├─ monitor          # 系统监控模块 | ||||||
|     │  │  └─ system           # 系统管理模块 |     │  │  └─ system           # 系统管理模块 | ||||||
|     │  ├─ assets            # 静态资源 |     │  ├─ assets            # 静态资源 | ||||||
|     │  │  ├─ images           # 图片资源 |     │  │  ├─ images           # 图片资源 | ||||||
|   | |||||||
| @@ -0,0 +1,113 @@ | |||||||
|  | /* | ||||||
|  |  * 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.common.annotation; | ||||||
|  |  | ||||||
|  | import java.lang.annotation.ElementType; | ||||||
|  | import java.lang.annotation.Retention; | ||||||
|  | import java.lang.annotation.RetentionPolicy; | ||||||
|  | import java.lang.annotation.Target; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 查询注解 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 18:01 | ||||||
|  |  */ | ||||||
|  | @Target(ElementType.FIELD) | ||||||
|  | @Retention(RetentionPolicy.RUNTIME) | ||||||
|  | public @interface Query { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 属性名(默认和使用该注解的属性的名称一致) | ||||||
|  |      */ | ||||||
|  |     String property() default ""; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 查询类型(等值查询、模糊查询、范围查询等) | ||||||
|  |      */ | ||||||
|  |     Type type() default Type.EQUAL; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 多属性模糊查询,仅支持 String 类型属性,多个属性之间用逗号分隔 | ||||||
|  |      * <p> | ||||||
|  |      * 例如:@Query(blurry = "username,email") 表示根据用户名和邮箱模糊查询 | ||||||
|  |      * </p> | ||||||
|  |      */ | ||||||
|  |     String blurry() default ""; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 查询类型 | ||||||
|  |      */ | ||||||
|  |     enum Type { | ||||||
|  |         /** | ||||||
|  |          * 等值查询,例如:WHERE `age` = 18 | ||||||
|  |          */ | ||||||
|  |         EQUAL, | ||||||
|  |         /** | ||||||
|  |          * 非等值查询,例如:WHERE `age` != 18 | ||||||
|  |          */ | ||||||
|  |         NOT_EQUAL, | ||||||
|  |         /** | ||||||
|  |          * 大于查询,例如:WHERE `age` > 18 | ||||||
|  |          */ | ||||||
|  |         GREATER_THAN, | ||||||
|  |         /** | ||||||
|  |          * 小于查询,例如:WHERE `age` < 18 | ||||||
|  |          */ | ||||||
|  |         LESS_THAN, | ||||||
|  |         /** | ||||||
|  |          * 大于等于查询,例如:WHERE `age` >= 18 | ||||||
|  |          */ | ||||||
|  |         GREATER_THAN_OR_EQUAL, | ||||||
|  |         /** | ||||||
|  |          * 小于等于查询,例如:WHERE `age` <= 18 | ||||||
|  |          */ | ||||||
|  |         LESS_THAN_OR_EQUAL, | ||||||
|  |         /** | ||||||
|  |          * 范围查询,例如:WHERE `age` BETWEEN 10 AND 18 | ||||||
|  |          */ | ||||||
|  |         BETWEEN, | ||||||
|  |         /** | ||||||
|  |          * 左模糊查询,例如:WHERE `nickname` LIKE '%张' | ||||||
|  |          */ | ||||||
|  |         LEFT_LIKE, | ||||||
|  |         /** | ||||||
|  |          * 中模糊查询,例如:WHERE `nickname` LIKE '%雪%' | ||||||
|  |          */ | ||||||
|  |         INNER_LIKE, | ||||||
|  |         /** | ||||||
|  |          * 右模糊查询,例如:WHERE `nickname` LIKE '雪%' | ||||||
|  |          */ | ||||||
|  |         RIGHT_LIKE, | ||||||
|  |         /** | ||||||
|  |          * 包含查询,例如:WHERE `age` IN (10, 20, 30) | ||||||
|  |          */ | ||||||
|  |         IN, | ||||||
|  |         /** | ||||||
|  |          * 不包含查询,例如:WHERE `age` NOT IN (20, 30) | ||||||
|  |          */ | ||||||
|  |         NOT_IN, | ||||||
|  |         /** | ||||||
|  |          * 空查询,例如:WHERE `email` IS NULL | ||||||
|  |          */ | ||||||
|  |         IS_NULL, | ||||||
|  |         /** | ||||||
|  |          * 非空查询,例如:WHERE `email` IS NOT NULL | ||||||
|  |          */ | ||||||
|  |         NOT_NULL,; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -41,7 +41,7 @@ import cn.hutool.core.util.StrUtil; | |||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.exception.BadRequestException; | import top.charles7c.cnadmin.common.exception.BadRequestException; | ||||||
| import top.charles7c.cnadmin.common.exception.ServiceException; | import top.charles7c.cnadmin.common.exception.ServiceException; | ||||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | import top.charles7c.cnadmin.common.model.dto.LogContext; | ||||||
| import top.charles7c.cnadmin.common.model.vo.R; | import top.charles7c.cnadmin.common.model.vo.R; | ||||||
| import top.charles7c.cnadmin.common.util.ExceptionUtils; | import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||||
| import top.charles7c.cnadmin.common.util.StreamUtils; | import top.charles7c.cnadmin.common.util.StreamUtils; | ||||||
| @@ -179,15 +179,15 @@ public class GlobalExceptionHandler { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 操作日志保存异常信息 |      * 在系统日志上下文中保存异常信息 | ||||||
|      * |      * | ||||||
|      * @param e |      * @param e | ||||||
|      *            异常信息 |      *            异常信息 | ||||||
|      */ |      */ | ||||||
|     private void setException(Exception e) { |     private void setException(Exception e) { | ||||||
|         OperationLog operationLog = LogContextHolder.get(); |         LogContext logContext = LogContextHolder.get(); | ||||||
|         if (operationLog != null) { |         if (logContext != null) { | ||||||
|             operationLog.setException(e); |             logContext.setException(e); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -21,13 +21,13 @@ import java.time.LocalDateTime; | |||||||
| import lombok.Data; | import lombok.Data; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 操作日志 |  * 系统日志上下文 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/25 8:59 |  * @since 2022/12/25 8:59 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| public class OperationLog { | public class LogContext { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 操作人 |      * 操作人 | ||||||
| @@ -43,5 +43,4 @@ public class OperationLog { | |||||||
|      * 异常 |      * 异常 | ||||||
|      */ |      */ | ||||||
|     private Exception exception; |     private Exception exception; | ||||||
| 
 |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,127 @@ | |||||||
|  | /* | ||||||
|  |  * 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.common.model.query; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import org.springdoc.api.annotations.ParameterObject; | ||||||
|  | import org.springframework.data.domain.Sort; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.core.metadata.IPage; | ||||||
|  | import com.baomidou.mybatisplus.core.metadata.OrderItem; | ||||||
|  | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.util.ArrayUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 分页查询条件 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 10:59 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @ParameterObject | ||||||
|  | @Schema(description = "分页查询条件") | ||||||
|  | public class PageQuery implements Serializable { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 页码 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "页码") | ||||||
|  |     private int page; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 每页记录数 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "每页记录数") | ||||||
|  |     private int size; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 排序条件 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "排序条件", example = "sort=published,desc&sort=title,asc") | ||||||
|  |     private String[] sort; | ||||||
|  |  | ||||||
|  |     /** 默认页码:1 */ | ||||||
|  |     private static final int DEFAULT_PAGE = 1; | ||||||
|  |  | ||||||
|  |     /** 默认每页记录数:int 最大值 */ | ||||||
|  |     private static final int DEFAULT_SIZE = Integer.MAX_VALUE; | ||||||
|  |     private static final String DELIMITER = ","; | ||||||
|  |  | ||||||
|  |     public PageQuery() { | ||||||
|  |         this.page = DEFAULT_PAGE; | ||||||
|  |         this.size = DEFAULT_SIZE; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 解析排序条件为 Spring 分页排序实体 | ||||||
|  |      * | ||||||
|  |      * @return Spring 分页排序实体 | ||||||
|  |      */ | ||||||
|  |     public Sort getSort() { | ||||||
|  |         if (ArrayUtil.isEmpty(sort)) { | ||||||
|  |             return Sort.unsorted(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         List<Sort.Order> orders = new ArrayList<>(sort.length); | ||||||
|  |         if (sort[0].contains(DELIMITER)) { | ||||||
|  |             // e.g "sort=published,desc&sort=title,asc" | ||||||
|  |             for (String s : sort) { | ||||||
|  |                 String[] sortArr = s.split(DELIMITER); | ||||||
|  |                 Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sortArr[1].toUpperCase()), sortArr[0]); | ||||||
|  |                 orders.add(order); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             // e.g "sort=published,desc" | ||||||
|  |             Sort.Order order = new Sort.Order(Sort.Direction.valueOf(sort[1].toUpperCase()), sort[0]); | ||||||
|  |             orders.add(order); | ||||||
|  |         } | ||||||
|  |         return Sort.by(orders); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 基于分页查询条件转换为 MyBatis Plus 分页条件 | ||||||
|  |      * | ||||||
|  |      * @param <T> | ||||||
|  |      *            列表数据类型 | ||||||
|  |      * @return MyBatis Plus 分页条件 | ||||||
|  |      */ | ||||||
|  |     public <T> IPage<T> toPage() { | ||||||
|  |         Page<T> mybatisPage = new Page<>(this.getPage(), this.getSize()); | ||||||
|  |         Sort pageSort = this.getSort(); | ||||||
|  |         if (CollUtil.isNotEmpty(pageSort)) { | ||||||
|  |             for (Sort.Order order : pageSort) { | ||||||
|  |                 OrderItem orderItem = new OrderItem(); | ||||||
|  |                 orderItem.setAsc(order.isAscending()); | ||||||
|  |                 orderItem.setColumn(StrUtil.toUnderlineCase(order.getProperty())); | ||||||
|  |                 mybatisPage.addOrder(orderItem); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return mybatisPage; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,96 @@ | |||||||
|  | /* | ||||||
|  |  * 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.common.model.vo; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  | import lombok.experimental.Accessors; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.core.metadata.IPage; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.bean.BeanUtil; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 分页信息 | ||||||
|  |  * | ||||||
|  |  * @param <V> | ||||||
|  |  *            列表数据类型 | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/14 23:40 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Accessors(chain = true) | ||||||
|  | @Schema(description = "分页信息") | ||||||
|  | public class PageInfo<V> { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 列表数据 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "列表数据") | ||||||
|  |     private List<V> list; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 总记录数 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "总记录数") | ||||||
|  |     private long total; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 基于 MyBatis Plus 分页数据构建分页信息,并将源数据转换为指定类型数据 | ||||||
|  |      * | ||||||
|  |      * @param page | ||||||
|  |      *            MyBatis Plus 分页数据 | ||||||
|  |      * @param targetClass | ||||||
|  |      *            目标类型 Class 对象 | ||||||
|  |      * @param <T> | ||||||
|  |      *            源列表数据类型 | ||||||
|  |      * @param <V> | ||||||
|  |      *            目标列表数据类型 | ||||||
|  |      * @return 分页信息 | ||||||
|  |      */ | ||||||
|  |     public static <T, V> PageInfo<V> build(IPage<T> page, Class<V> targetClass) { | ||||||
|  |         if (page == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         PageInfo<V> pageInfo = new PageInfo<>(); | ||||||
|  |         pageInfo.setList(BeanUtil.copyToList(page.getRecords(), targetClass)); | ||||||
|  |         pageInfo.setTotal(page.getTotal()); | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 基于 MyBatis Plus 分页数据构建分页信息 | ||||||
|  |      * | ||||||
|  |      * @param page | ||||||
|  |      *            MyBatis Plus 分页数据 | ||||||
|  |      * @param <V> | ||||||
|  |      *            列表数据类型 | ||||||
|  |      * @return 分页信息 | ||||||
|  |      */ | ||||||
|  |     public static <V> PageInfo<V> build(IPage<V> page) { | ||||||
|  |         if (page == null) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         PageInfo<V> pageInfo = new PageInfo<>(); | ||||||
|  |         pageInfo.setList(page.getRecords()); | ||||||
|  |         pageInfo.setTotal(pageInfo.getTotal()); | ||||||
|  |         return pageInfo; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -16,7 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.common.model.vo; | package top.charles7c.cnadmin.common.model.vo; | ||||||
|  |  | ||||||
| import java.io.Serializable; |  | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| import lombok.AccessLevel; | import lombok.AccessLevel; | ||||||
| @@ -36,9 +35,7 @@ import org.springframework.http.HttpStatus; | |||||||
| @Data | @Data | ||||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||||
| @Schema(description = "响应信息") | @Schema(description = "响应信息") | ||||||
| public class R<V extends Serializable> implements Serializable { | public class R<V> { | ||||||
|  |  | ||||||
|     private static final long serialVersionUID = 1L; |  | ||||||
|  |  | ||||||
|     /** 是否成功 */ |     /** 是否成功 */ | ||||||
|     @Schema(description = "是否成功") |     @Schema(description = "是否成功") | ||||||
| @@ -72,39 +69,39 @@ public class R<V extends Serializable> implements Serializable { | |||||||
|         this.data = data; |         this.data = data; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> ok() { |     public static <V> R<V> ok() { | ||||||
|         return new R<>(true, SUCCESS_CODE, "操作成功", null); |         return new R<>(true, SUCCESS_CODE, "操作成功", null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> ok(V data) { |     public static <V> R<V> ok(V data) { | ||||||
|         return new R<>(true, SUCCESS_CODE, "操作成功", data); |         return new R<>(true, SUCCESS_CODE, "操作成功", data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> ok(String msg) { |     public static <V> R<V> ok(String msg) { | ||||||
|         return new R<>(true, SUCCESS_CODE, msg, null); |         return new R<>(true, SUCCESS_CODE, msg, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> ok(String msg, V data) { |     public static <V> R<V> ok(String msg, V data) { | ||||||
|         return new R<>(true, SUCCESS_CODE, msg, data); |         return new R<>(true, SUCCESS_CODE, msg, data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> fail() { |     public static <V> R<V> fail() { | ||||||
|         return new R<>(false, FAIL_CODE, "操作失败", null); |         return new R<>(false, FAIL_CODE, "操作失败", null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> fail(String msg) { |     public static <V> R<V> fail(String msg) { | ||||||
|         return new R<>(false, FAIL_CODE, msg, null); |         return new R<>(false, FAIL_CODE, msg, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> fail(V data) { |     public static <V> R<V> fail(V data) { | ||||||
|         return new R<>(false, FAIL_CODE, "操作失败", data); |         return new R<>(false, FAIL_CODE, "操作失败", data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> fail(String msg, V data) { |     public static <V> R<V> fail(String msg, V data) { | ||||||
|         return new R<>(false, FAIL_CODE, msg, data); |         return new R<>(false, FAIL_CODE, msg, data); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static <V extends Serializable> R<V> fail(int code, String msg) { |     public static <V> R<V> fail(int code, String msg) { | ||||||
|         return new R<>(false, code, msg, null); |         return new R<>(false, code, msg, null); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,66 @@ | |||||||
|  | /* | ||||||
|  |  * 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.common.util; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  | import java.lang.reflect.Modifier; | ||||||
|  | import java.util.Arrays; | ||||||
|  |  | ||||||
|  | import lombok.AccessLevel; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.util.ReflectUtil; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 反射工具类 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 22:05 | ||||||
|  |  */ | ||||||
|  | @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||||
|  | public class ReflectUtils { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获得一个类中所有非静态字段名列表,包括其父类中的字段<br> | ||||||
|  |      * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 | ||||||
|  |      * | ||||||
|  |      * @param beanClass | ||||||
|  |      *            类 | ||||||
|  |      * @return 非静态字段名列表 | ||||||
|  |      * @throws SecurityException | ||||||
|  |      *             安全检查异常 | ||||||
|  |      */ | ||||||
|  |     public static String[] getNonStaticFieldsName(Class<?> beanClass) throws SecurityException { | ||||||
|  |         Field[] nonStaticFields = getNonStaticFields(beanClass); | ||||||
|  |         return Arrays.stream(nonStaticFields).map(Field::getName).toArray(String[]::new); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获得一个类中所有非静态字段列表,包括其父类中的字段<br> | ||||||
|  |      * 如果子类与父类中存在同名字段,则这两个字段同时存在,子类字段在前,父类字段在后。 | ||||||
|  |      * | ||||||
|  |      * @param beanClass | ||||||
|  |      *            类 | ||||||
|  |      * @return 非静态字段列表 | ||||||
|  |      * @throws SecurityException | ||||||
|  |      *             安全检查异常 | ||||||
|  |      */ | ||||||
|  |     public static Field[] getNonStaticFields(Class<?> beanClass) throws SecurityException { | ||||||
|  |         Field[] fields = ReflectUtil.getFields(beanClass); | ||||||
|  |         return Arrays.stream(fields).filter(f -> !Modifier.isStatic(f.getModifiers())).toArray(Field[]::new); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,216 @@ | |||||||
|  | /* | ||||||
|  |  * 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.common.util.helper; | ||||||
|  |  | ||||||
|  | import java.lang.reflect.Field; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.AccessLevel; | ||||||
|  | import lombok.NoArgsConstructor; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
|  | import com.baomidou.mybatisplus.core.toolkit.Wrappers; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.core.util.ObjectUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.annotation.Query; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 查询助手 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 18:17 | ||||||
|  |  */ | ||||||
|  | @Slf4j | ||||||
|  | @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||||
|  | public class QueryHelper { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 根据查询条件构建 MyBatis Plus 查询条件封装对象 | ||||||
|  |      * | ||||||
|  |      * @param query | ||||||
|  |      *            查询条件 | ||||||
|  |      * @param <Q> | ||||||
|  |      *            查询条件数据类型 | ||||||
|  |      * @param <R> | ||||||
|  |      *            查询数据类型 | ||||||
|  |      * @return MyBatis Plus 查询条件封装对象 | ||||||
|  |      */ | ||||||
|  |     public static <Q, R> QueryWrapper<R> build(Q query) { | ||||||
|  |         QueryWrapper<R> queryWrapper = Wrappers.query(); | ||||||
|  |         // 没有查询条件,直接返回 | ||||||
|  |         if (query == null) { | ||||||
|  |             return queryWrapper; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 获取查询条件中所有的属性(包括私有的和父类的) | ||||||
|  |         List<Field> fieldList = getFieldList(query.getClass(), new ArrayList<>()); | ||||||
|  |         fieldList.forEach(field -> buildQuery(query, field, queryWrapper)); | ||||||
|  |         return queryWrapper; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 获取指定类的所有属性(包括私有的和父类的) | ||||||
|  |      * | ||||||
|  |      * @param clazz | ||||||
|  |      *            指定类 | ||||||
|  |      * @param fieldList | ||||||
|  |      *            属性列表 | ||||||
|  |      * @param <Q> | ||||||
|  |      *            查询条件数据类型 | ||||||
|  |      * @return 属性列表(包括私有的和父类的) | ||||||
|  |      */ | ||||||
|  |     public static <Q> List<Field> getFieldList(Class<Q> clazz, List<Field> fieldList) { | ||||||
|  |         if (clazz != null) { | ||||||
|  |             fieldList.addAll(Arrays.asList(clazz.getDeclaredFields())); | ||||||
|  |             getFieldList(clazz.getSuperclass(), fieldList); | ||||||
|  |         } | ||||||
|  |         return fieldList; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 构建 MyBatis Plus 查询条件封装对象 | ||||||
|  |      * | ||||||
|  |      * @param query | ||||||
|  |      *            查询条件 | ||||||
|  |      * @param field | ||||||
|  |      *            属性 | ||||||
|  |      * @param queryWrapper | ||||||
|  |      *            MyBatis Plus 查询条件封装对象 | ||||||
|  |      * @param <Q> | ||||||
|  |      *            查询条件数据类型 | ||||||
|  |      * @param <R> | ||||||
|  |      *            查询数据类型 | ||||||
|  |      */ | ||||||
|  |     private static <Q, R> void buildQuery(Q query, Field field, QueryWrapper<R> queryWrapper) { | ||||||
|  |         boolean accessible = field.isAccessible(); | ||||||
|  |         try { | ||||||
|  |             field.setAccessible(true); | ||||||
|  |             // 没有 @Query,直接返回 | ||||||
|  |             Query queryAnnotation = field.getAnnotation(Query.class); | ||||||
|  |             if (queryAnnotation == null) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 如果属性值为空,直接返回 | ||||||
|  |             Object fieldValue = field.get(query); | ||||||
|  |             if (ObjectUtil.isEmpty(fieldValue)) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 解析查询条件 | ||||||
|  |             parse(queryAnnotation, field.getName(), fieldValue, queryWrapper); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             log.error(e.getMessage(), e); | ||||||
|  |         } finally { | ||||||
|  |             field.setAccessible(accessible); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 解析查询条件 | ||||||
|  |      * | ||||||
|  |      * @param queryAnnotation | ||||||
|  |      *            查询注解 | ||||||
|  |      * @param fieldName | ||||||
|  |      *            属性名 | ||||||
|  |      * @param fieldValue | ||||||
|  |      *            属性值 | ||||||
|  |      * @param queryWrapper | ||||||
|  |      *            MyBatis Plus 查询条件封装对象 | ||||||
|  |      * @param <R> | ||||||
|  |      *            查询数据类型 | ||||||
|  |      */ | ||||||
|  |     private static <R> void parse(Query queryAnnotation, String fieldName, Object fieldValue, | ||||||
|  |         QueryWrapper<R> queryWrapper) { | ||||||
|  |         // 解析多属性模糊查询 | ||||||
|  |         // 如果设置了多属性模糊查询,分割属性进行条件拼接 | ||||||
|  |         String blurry = queryAnnotation.blurry(); | ||||||
|  |         if (StrUtil.isNotBlank(blurry)) { | ||||||
|  |             String[] propertyArr = blurry.split(","); | ||||||
|  |             queryWrapper.and(wrapper -> { | ||||||
|  |                 for (String property : propertyArr) { | ||||||
|  |                     wrapper.or().like(StrUtil.toUnderlineCase(property), fieldValue); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 解析单个属性查询 | ||||||
|  |         // 如果没有单独指定属性名,就和使用该注解的属性的名称一致 | ||||||
|  |         // 注意:数据库规范中列采用下划线连接法命名,程序规范中变量采用驼峰法命名 | ||||||
|  |         String property = queryAnnotation.property(); | ||||||
|  |         fieldName = StrUtil.isNotBlank(property) ? property : fieldName; | ||||||
|  |         String columnName = StrUtil.toUnderlineCase(fieldName); | ||||||
|  |         switch (queryAnnotation.type()) { | ||||||
|  |             case EQUAL: | ||||||
|  |                 queryWrapper.eq(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case NOT_EQUAL: | ||||||
|  |                 queryWrapper.ne(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case GREATER_THAN: | ||||||
|  |                 queryWrapper.gt(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case LESS_THAN: | ||||||
|  |                 queryWrapper.lt(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case GREATER_THAN_OR_EQUAL: | ||||||
|  |                 queryWrapper.ge(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case LESS_THAN_OR_EQUAL: | ||||||
|  |                 queryWrapper.le(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case BETWEEN: | ||||||
|  |                 List<Object> between = new ArrayList<>((List<Object>)fieldValue); | ||||||
|  |                 queryWrapper.between(columnName, between.get(0), between.get(1)); | ||||||
|  |                 break; | ||||||
|  |             case LEFT_LIKE: | ||||||
|  |                 queryWrapper.likeLeft(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case INNER_LIKE: | ||||||
|  |                 queryWrapper.like(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case RIGHT_LIKE: | ||||||
|  |                 queryWrapper.likeRight(columnName, fieldValue); | ||||||
|  |                 break; | ||||||
|  |             case IN: | ||||||
|  |                 if (CollUtil.isNotEmpty((List<Object>)fieldValue)) { | ||||||
|  |                     queryWrapper.in(columnName, (List<Object>)fieldValue); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case NOT_IN: | ||||||
|  |                 if (CollUtil.isNotEmpty((List<Object>)fieldValue)) { | ||||||
|  |                     queryWrapper.notIn(columnName, (List<Object>)fieldValue); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case IS_NULL: | ||||||
|  |                 queryWrapper.isNull(columnName); | ||||||
|  |                 break; | ||||||
|  |             case NOT_NULL: | ||||||
|  |                 queryWrapper.isNotNull(columnName); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -19,10 +19,10 @@ package top.charles7c.cnadmin.common.util.holder; | |||||||
| import lombok.AccessLevel; | import lombok.AccessLevel; | ||||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | import top.charles7c.cnadmin.common.model.dto.LogContext; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志上下文持有者 |  * 系统日志上下文持有者 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/25 8:55 |  * @since 2022/12/25 8:55 | ||||||
| @@ -30,29 +30,29 @@ import top.charles7c.cnadmin.common.model.dto.OperationLog; | |||||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||||
| public class LogContextHolder { | public class LogContextHolder { | ||||||
|  |  | ||||||
|     private static final ThreadLocal<OperationLog> LOG_THREAD_LOCAL = new ThreadLocal<>(); |     private static final ThreadLocal<LogContext> LOG_THREAD_LOCAL = new ThreadLocal<>(); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 存储操作日志 |      * 存储系统日志上下文 | ||||||
|      * |      * | ||||||
|      * @param operationLog |      * @param logContext | ||||||
|      *            操作日志信息 |      *            系统日志上下文信息 | ||||||
|      */ |      */ | ||||||
|     public static void set(OperationLog operationLog) { |     public static void set(LogContext logContext) { | ||||||
|         LOG_THREAD_LOCAL.set(operationLog); |         LOG_THREAD_LOCAL.set(logContext); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 获取操作日志 |      * 获取系统日志上下文 | ||||||
|      * |      * | ||||||
|      * @return 操作日志信息 |      * @return 系统日志上下文信息 | ||||||
|      */ |      */ | ||||||
|     public static OperationLog get() { |     public static LogContext get() { | ||||||
|         return LOG_THREAD_LOCAL.get(); |         return LOG_THREAD_LOCAL.get(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 移除操作日志 |      * 移除系统日志上下文 | ||||||
|      */ |      */ | ||||||
|     public static void remove() { |     public static void remove() { | ||||||
|         LOG_THREAD_LOCAL.remove(); |         LOG_THREAD_LOCAL.remove(); | ||||||
|   | |||||||
| @@ -32,10 +32,10 @@ limitations under the License. | |||||||
|     <description>系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)</description> |     <description>系统监控模块(存放系统监控模块相关功能,例如:日志管理、服务监控等)</description> | ||||||
|  |  | ||||||
|     <dependencies> |     <dependencies> | ||||||
|         <!-- 公共模块(存放公共工具类,公共配置等) --> |         <!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) --> | ||||||
|         <dependency> |         <dependency> | ||||||
|             <groupId>top.charles7c</groupId> |             <groupId>top.charles7c</groupId> | ||||||
|             <artifactId>continew-admin-common</artifactId> |             <artifactId>continew-admin-system</artifactId> | ||||||
|         </dependency> |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
| </project> | </project> | ||||||
| @@ -19,7 +19,7 @@ package top.charles7c.cnadmin.monitor.annotation; | |||||||
| import java.lang.annotation.*; | import java.lang.annotation.*; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志注解(用于接口方法或类上) |  * 系统日志注解(用于接口方法或类上) | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/23 20:00 |  * @since 2022/12/23 20:00 | ||||||
| @@ -30,7 +30,7 @@ import java.lang.annotation.*; | |||||||
| public @interface Log { | public @interface Log { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 操作日志描述 |      * 日志描述 | ||||||
|      */ |      */ | ||||||
|     String value() default ""; |     String value() default ""; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -25,28 +25,33 @@ import org.springframework.boot.context.properties.ConfigurationProperties; | |||||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志配置属性 |  * 系统日志配置属性 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/24 23:04 |  * @since 2022/12/24 23:04 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @Component | @Component | ||||||
| @ConfigurationProperties(prefix = "logging.operation") | @ConfigurationProperties(prefix = "logging.system") | ||||||
| public class LogProperties { | public class LogProperties { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 是否启用操作日志 |      * 是否启用系统日志 | ||||||
|      */ |      */ | ||||||
|     private Boolean enabled = false; |     private Boolean enabled; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 是否记录内网 IP 操作 | ||||||
|  |      */ | ||||||
|  |     private Boolean includeInnerIp; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 哪些请求方式不记录系统日志 | ||||||
|  |      */ | ||||||
|  |     private List<String> excludeMethods = new ArrayList<>(); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 脱敏字段 |      * 脱敏字段 | ||||||
|      */ |      */ | ||||||
|     private List<String> desensitize = new ArrayList<>(); |     private List<String> desensitize = new ArrayList<>(); | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 不记录操作日志的请求方式 |  | ||||||
|      */ |  | ||||||
|     private List<String> excludeMethods = new ArrayList<>(); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,22 +19,24 @@ package top.charles7c.cnadmin.monitor.enums; | |||||||
| import lombok.Getter; | import lombok.Getter; | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| 
 | 
 | ||||||
|  | import com.baomidou.mybatisplus.annotation.IEnum; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * 操作日志级别枚举 |  * 操作结果枚举 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/25 9:09 |  * @since 2022/12/25 9:09 | ||||||
|  */ |  */ | ||||||
| @Getter | @Getter | ||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| public enum LogLevelEnum { | public enum LogResultEnum implements IEnum<Integer> { | ||||||
| 
 | 
 | ||||||
|     /** 普通 */ |     /** 成功 */ | ||||||
|     INFO("普通"), |     SUCCESS(1, "成功"), | ||||||
| 
 | 
 | ||||||
|     /** 错误 */ |     /** 失败 */ | ||||||
|     ERROR("错误"),; |     FAILURE(2, "失败"),; | ||||||
| 
 | 
 | ||||||
|     /** 描述 */ |     private final Integer value; | ||||||
|     private final String description; |     private final String description; | ||||||
| } | } | ||||||
| @@ -42,20 +42,21 @@ import cn.hutool.core.exceptions.ExceptionUtil; | |||||||
| import cn.hutool.core.util.StrUtil; | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.hutool.extra.servlet.ServletUtil; | import cn.hutool.extra.servlet.ServletUtil; | ||||||
| import cn.hutool.extra.spring.SpringUtil; | import cn.hutool.extra.spring.SpringUtil; | ||||||
|  | import cn.hutool.http.HttpStatus; | ||||||
| import cn.hutool.json.JSONUtil; | import cn.hutool.json.JSONUtil; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.model.dto.OperationLog; | import top.charles7c.cnadmin.common.model.dto.LogContext; | ||||||
| import top.charles7c.cnadmin.common.util.IpUtils; | import top.charles7c.cnadmin.common.util.IpUtils; | ||||||
| import top.charles7c.cnadmin.common.util.ServletUtils; | import top.charles7c.cnadmin.common.util.ServletUtils; | ||||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||||
| import top.charles7c.cnadmin.common.util.holder.LogContextHolder; | import top.charles7c.cnadmin.common.util.holder.LogContextHolder; | ||||||
| import top.charles7c.cnadmin.monitor.annotation.Log; | import top.charles7c.cnadmin.monitor.annotation.Log; | ||||||
| import top.charles7c.cnadmin.monitor.config.properties.LogProperties; | import top.charles7c.cnadmin.monitor.config.properties.LogProperties; | ||||||
| import top.charles7c.cnadmin.monitor.enums.LogLevelEnum; | import top.charles7c.cnadmin.monitor.enums.LogResultEnum; | ||||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志拦截器 |  * 系统日志拦截器 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/24 21:14 |  * @since 2022/12/24 21:14 | ||||||
| @@ -87,14 +88,14 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 记录描述 |         // 记录日志描述 | ||||||
|         this.logDescription(sysLog, handler); |         this.logDescription(sysLog, handler); | ||||||
|         // 记录请求信息 |         // 记录请求信息 | ||||||
|         this.logRequest(sysLog, request); |         this.logRequest(sysLog, request); | ||||||
|         // 记录响应信息 |         // 记录响应信息 | ||||||
|         this.logResponse(sysLog, response); |         this.logResponse(sysLog, response); | ||||||
|  |  | ||||||
|         // 保存操作日志 |         // 保存系统日志 | ||||||
|         SpringUtil.getApplicationContext().publishEvent(sysLog); |         SpringUtil.getApplicationContext().publishEvent(sysLog); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -102,31 +103,31 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|      * 记录操作时间 |      * 记录操作时间 | ||||||
|      */ |      */ | ||||||
|     private void logCreateTime() { |     private void logCreateTime() { | ||||||
|         OperationLog operationLog = new OperationLog(); |         LogContext logContext = new LogContext(); | ||||||
|         operationLog.setCreateUser(LoginHelper.getUserId()); |         logContext.setCreateUser(LoginHelper.getUserId()); | ||||||
|         operationLog.setCreateTime(LocalDateTime.now()); |         logContext.setCreateTime(LocalDateTime.now()); | ||||||
|         LogContextHolder.set(operationLog); |         LogContextHolder.set(logContext); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 记录请求耗时及异常信息 |      * 记录请求耗时及异常信息 | ||||||
|      * |      * | ||||||
|      * @return 日志信息 |      * @return 系统日志信息 | ||||||
|      */ |      */ | ||||||
|     private SysLog logElapsedTimeAndException() { |     private SysLog logElapsedTimeAndException() { | ||||||
|         OperationLog operationLog = LogContextHolder.get(); |         LogContext logContext = LogContextHolder.get(); | ||||||
|         if (operationLog != null) { |         if (logContext != null) { | ||||||
|             LogContextHolder.remove(); |             LogContextHolder.remove(); | ||||||
|             SysLog sysLog = new SysLog(); |             SysLog sysLog = new SysLog(); | ||||||
|             sysLog.setCreateTime(operationLog.getCreateTime()); |             sysLog.setCreateTime(logContext.getCreateTime()); | ||||||
|             sysLog.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(sysLog.getCreateTime())); |             sysLog.setElapsedTime(System.currentTimeMillis() - LocalDateTimeUtil.toEpochMilli(sysLog.getCreateTime())); | ||||||
|             sysLog.setLogLevel(LogLevelEnum.INFO); |             sysLog.setResult(LogResultEnum.SUCCESS); | ||||||
|  |  | ||||||
|             // 记录异常信息 |             // 记录异常信息 | ||||||
|             Exception exception = operationLog.getException(); |             Exception exception = logContext.getException(); | ||||||
|             if (exception != null) { |             if (exception != null) { | ||||||
|                 sysLog.setLogLevel(LogLevelEnum.ERROR); |                 sysLog.setResult(LogResultEnum.FAILURE); | ||||||
|                 sysLog.setException(ExceptionUtil.stacktraceToString(operationLog.getException(), -1)); |                 sysLog.setException(ExceptionUtil.stacktraceToString(exception, -1)); | ||||||
|             } |             } | ||||||
|             return sysLog; |             return sysLog; | ||||||
|         } |         } | ||||||
| @@ -137,7 +138,7 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|      * 记录日志描述 |      * 记录日志描述 | ||||||
|      * |      * | ||||||
|      * @param sysLog |      * @param sysLog | ||||||
|      *            日志信息 |      *            系统日志信息 | ||||||
|      * @param handler |      * @param handler | ||||||
|      *            处理器 |      *            处理器 | ||||||
|      */ |      */ | ||||||
| @@ -148,7 +149,7 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|  |  | ||||||
|         if (methodOperation != null) { |         if (methodOperation != null) { | ||||||
|             sysLog.setDescription( |             sysLog.setDescription( | ||||||
|                 StrUtil.isNotBlank(methodOperation.summary()) ? methodOperation.summary() : "请在该接口方法上指定操作日志描述"); |                 StrUtil.isNotBlank(methodOperation.summary()) ? methodOperation.summary() : "请在该接口方法上指定日志描述"); | ||||||
|         } |         } | ||||||
|         // 例如:@Log("获取验证码") -> 获取验证码 |         // 例如:@Log("获取验证码") -> 获取验证码 | ||||||
|         if (methodLog != null && StrUtil.isNotBlank(methodLog.value())) { |         if (methodLog != null && StrUtil.isNotBlank(methodLog.value())) { | ||||||
| @@ -160,7 +161,7 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|      * 记录请求信息 |      * 记录请求信息 | ||||||
|      * |      * | ||||||
|      * @param sysLog |      * @param sysLog | ||||||
|      *            日志信息 |      *            系统日志信息 | ||||||
|      * @param request |      * @param request | ||||||
|      *            请求对象 |      *            请求对象 | ||||||
|      */ |      */ | ||||||
| @@ -184,18 +185,21 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|      * 记录响应信息 |      * 记录响应信息 | ||||||
|      * |      * | ||||||
|      * @param sysLog |      * @param sysLog | ||||||
|      *            日志信息 |      *            系统日志信息 | ||||||
|      * @param response |      * @param response | ||||||
|      *            响应对象 |      *            响应对象 | ||||||
|      */ |      */ | ||||||
|     private void logResponse(SysLog sysLog, HttpServletResponse response) { |     private void logResponse(SysLog sysLog, HttpServletResponse response) { | ||||||
|         sysLog.setStatusCode(response.getStatus()); |         int status = response.getStatus(); | ||||||
|  |         sysLog.setStatusCode(status); | ||||||
|         sysLog.setResponseHeader(this.desensitize(ServletUtil.getHeadersMap(response))); |         sysLog.setResponseHeader(this.desensitize(ServletUtil.getHeadersMap(response))); | ||||||
|         // 响应体(不记录非 JSON 响应数据) |         // 响应体(不记录非 JSON 响应数据) | ||||||
|         String responseBody = this.getResponseBody(response); |         String responseBody = this.getResponseBody(response); | ||||||
|         if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) { |         if (StrUtil.isNotBlank(responseBody) && JSONUtil.isTypeJSON(responseBody)) { | ||||||
|             sysLog.setResponseBody(responseBody); |             sysLog.setResponseBody(responseBody); | ||||||
|         } |         } | ||||||
|  |         // 操作失败:>= 400 | ||||||
|  |         sysLog.setResult(status >= HttpStatus.HTTP_BAD_REQUEST ? LogResultEnum.FAILURE : sysLog.getResult()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -258,7 +262,7 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 检查是否要记录操作日志 |      * 检查是否要记录系统日志 | ||||||
|      * |      * | ||||||
|      * @param handler |      * @param handler | ||||||
|      *            / |      *            / | ||||||
| @@ -267,28 +271,34 @@ public class LogInterceptor implements HandlerInterceptor { | |||||||
|      * @return true 需要记录,false 不需要记录 |      * @return true 需要记录,false 不需要记录 | ||||||
|      */ |      */ | ||||||
|     private boolean checkIsNeedRecord(Object handler, HttpServletRequest request) { |     private boolean checkIsNeedRecord(Object handler, HttpServletRequest request) { | ||||||
|         // 1、未启用时,不需要记录操作日志 |         // 1、未启用时,不需要记录系统日志 | ||||||
|         if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) { |         if (!(handler instanceof HandlerMethod) || Boolean.FALSE.equals(operationLogProperties.getEnabled())) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 2、排除不需要记录日志的接口 |         // 2、检查是否需要记录内网 IP 操作 | ||||||
|  |         boolean isInnerIp = IpUtils.isInnerIP(ServletUtil.getClientIP(request)); | ||||||
|  |         if (isInnerIp && Boolean.FALSE.equals(operationLogProperties.getIncludeInnerIp())) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // 3、排除不需要记录系统日志的接口 | ||||||
|         HandlerMethod handlerMethod = (HandlerMethod)handler; |         HandlerMethod handlerMethod = (HandlerMethod)handler; | ||||||
|         Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class); |         Log methodLog = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Log.class); | ||||||
|         // 2.1 请求方式不要求记录且请求上没有 @Log 注解,则不记录操作日志 |         // 3.1 请求方式不要求记录且请求上没有 @Log 注解,则不记录系统日志 | ||||||
|         if (operationLogProperties.getExcludeMethods().contains(request.getMethod()) && methodLog == null) { |         if (operationLogProperties.getExcludeMethods().contains(request.getMethod()) && methodLog == null) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         // 2.2 如果接口上既没有 @Log 注解,也没有 @Operation 注解,则不记录操作日志 |         // 3.2 如果接口上既没有 @Log 注解,也没有 @Operation 注解,则不记录系统日志 | ||||||
|         Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class); |         Operation methodOperation = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Operation.class); | ||||||
|         if (methodLog == null && methodOperation == null) { |         if (methodLog == null && methodOperation == null) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         // 2.3 如果接口被隐藏,不记录操作日志 |         // 3.3 如果接口被隐藏,不记录系统日志 | ||||||
|         if (methodOperation != null && methodOperation.hidden()) { |         if (methodOperation != null && methodOperation.hidden()) { | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
|         // 2.4 如果接口上有 @Log 注解,但是要求忽略该接口,则不记录操作日志 |         // 3.4 如果接口上有 @Log 注解,但是要求忽略该接口,则不记录系统日志 | ||||||
|         return methodLog == null || !methodLog.ignore(); |         return methodLog == null || !methodLog.ignore(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | |||||||
| import top.charles7c.cnadmin.monitor.model.entity.SysLog; | import top.charles7c.cnadmin.monitor.model.entity.SysLog; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志 Mapper |  * 系统日志 Mapper | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/22 21:47 |  * @since 2022/12/22 21:47 | ||||||
|   | |||||||
| @@ -24,10 +24,10 @@ import lombok.Data; | |||||||
| 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 top.charles7c.cnadmin.monitor.enums.LogLevelEnum; | import top.charles7c.cnadmin.monitor.enums.LogResultEnum; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志实体 |  * 系统日志实体 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/25 9:11 |  * @since 2022/12/25 9:11 | ||||||
| @@ -44,18 +44,13 @@ public class SysLog implements Serializable { | |||||||
|     @TableId |     @TableId | ||||||
|     private Long logId; |     private Long logId; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 日志级别 |  | ||||||
|      */ |  | ||||||
|     private LogLevelEnum logLevel; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 日志描述 |      * 日志描述 | ||||||
|      */ |      */ | ||||||
|     private String description; |     private String description; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 请求 URL |      * 请求URL | ||||||
|      */ |      */ | ||||||
|     private String requestUrl; |     private String requestUrl; | ||||||
|  |  | ||||||
| @@ -95,12 +90,17 @@ public class SysLog implements Serializable { | |||||||
|     private Long elapsedTime; |     private Long elapsedTime; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 请求IP |      * 操作结果(1成功 2失败) | ||||||
|  |      */ | ||||||
|  |     private LogResultEnum result; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作IP | ||||||
|      */ |      */ | ||||||
|     private String requestIp; |     private String requestIp; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 操作地址 |      * 操作地点 | ||||||
|      */ |      */ | ||||||
|     private String location; |     private String location; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | /* | ||||||
|  |  * 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 static top.charles7c.cnadmin.common.annotation.Query.Type; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import org.springdoc.api.annotations.ParameterObject; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.annotation.Query; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 操作日志查询条件 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 11:43 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @ParameterObject | ||||||
|  | @Schema(description = "操作日志查询条件") | ||||||
|  | public class OperationLogQuery implements Serializable { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作人 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "操作人") | ||||||
|  |     @Query(property = "createUser", type = Type.EQUAL) | ||||||
|  |     private Long uid; | ||||||
|  | } | ||||||
| @@ -0,0 +1,95 @@ | |||||||
|  | /* | ||||||
|  |  * 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 com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.monitor.enums.LogResultEnum; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 操作日志信息 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/14 18:27 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "操作日志信息") | ||||||
|  | public class OperationLogVO 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 LogResultEnum result; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作IP | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "操作IP") | ||||||
|  |     private String requestIp; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作地点 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "操作地点") | ||||||
|  |     private String location; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 浏览器 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "浏览器") | ||||||
|  |     private String browser; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作人 | ||||||
|  |      */ | ||||||
|  |     @JsonIgnore | ||||||
|  |     private Long createUser; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作人 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "操作人") | ||||||
|  |     private String createUserString; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 操作时间 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "操作时间") | ||||||
|  |     private LocalDateTime createTime; | ||||||
|  | } | ||||||
| @@ -17,7 +17,7 @@ | |||||||
| package top.charles7c.cnadmin.monitor.service; | package top.charles7c.cnadmin.monitor.service; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志业务接口 |  * 系统日志业务接口 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/23 20:12 |  * @since 2022/12/23 20:12 | ||||||
|   | |||||||
| @@ -0,0 +1,42 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | 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; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 操作日志业务接口 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/15 21:05 | ||||||
|  |  */ | ||||||
|  | public interface OperationLogService { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 分页查询列表 | ||||||
|  |      * | ||||||
|  |      * @param query | ||||||
|  |      *            查询条件 | ||||||
|  |      * @param pageQuery | ||||||
|  |      *            分页查询条件 | ||||||
|  |      * @return 分页信息 | ||||||
|  |      */ | ||||||
|  |     PageInfo<OperationLogVO> list(OperationLogQuery query, PageQuery pageQuery); | ||||||
|  | } | ||||||
| @@ -28,7 +28,7 @@ import top.charles7c.cnadmin.monitor.model.entity.SysLog; | |||||||
| import top.charles7c.cnadmin.monitor.service.LogService; | import top.charles7c.cnadmin.monitor.service.LogService; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 操作日志业务实现类 |  * 系统日志业务实现类 | ||||||
|  * |  * | ||||||
|  * @author Charles7c |  * @author Charles7c | ||||||
|  * @since 2022/12/23 20:12 |  * @since 2022/12/23 20:12 | ||||||
|   | |||||||
| @@ -0,0 +1,90 @@ | |||||||
|  | /* | ||||||
|  |  * 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); | ||||||
|  |         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.getNickname()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								continew-admin-ui/src/api/monitor/operation-log.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								continew-admin-ui/src/api/monitor/operation-log.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | import axios from 'axios'; | ||||||
|  | import qs from 'query-string'; | ||||||
|  |  | ||||||
|  | export interface OperationLogRecord { | ||||||
|  |   logId: string; | ||||||
|  |   description: string; | ||||||
|  |   result: number, | ||||||
|  |   requestIp: string, | ||||||
|  |   location: string, | ||||||
|  |   browser: string, | ||||||
|  |   createUserString: string; | ||||||
|  |   createTime: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface OperationLogParams extends Partial<OperationLogRecord> { | ||||||
|  |   page: number; | ||||||
|  |   size: number; | ||||||
|  |   sort: Array<string>; | ||||||
|  |   uid: string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface OperationLogListRes { | ||||||
|  |   list: OperationLogRecord[]; | ||||||
|  |   total: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function queryOperationLogList(params: OperationLogParams) { | ||||||
|  |   return axios.get<OperationLogListRes>('/monitor/log/operation', { | ||||||
|  |     params, | ||||||
|  |     paramsSerializer: (obj) => { | ||||||
|  |       return qs.stringify(obj); | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|  | } | ||||||
| @@ -90,5 +90,8 @@ body { | |||||||
|     &.pass { |     &.pass { | ||||||
|       background-color: rgb(var(--green-6)); |       background-color: rgb(var(--green-6)); | ||||||
|     } |     } | ||||||
|  |     &.fail { | ||||||
|  |       background-color: rgb(var(--red-6)); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ import useAppStore from '../app'; | |||||||
|  |  | ||||||
| const useLoginStore = defineStore('user', { | const useLoginStore = defineStore('user', { | ||||||
|   state: (): UserState => ({ |   state: (): UserState => ({ | ||||||
|     userId: 1, |     userId: '', | ||||||
|     username: '', |     username: '', | ||||||
|     nickname: '', |     nickname: '', | ||||||
|     gender: 0, |     gender: 0, | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| export type RoleType = '' | '*' | 'admin' | 'user'; | export type RoleType = '' | '*' | 'admin' | 'user'; | ||||||
| export interface UserState { | export interface UserState { | ||||||
|   userId: number; |   userId: string; | ||||||
|   username: string; |   username: string; | ||||||
|   nickname: string; |   nickname: string; | ||||||
|   gender: number; |   gender: number; | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ | |||||||
|     captchaImgBase64.value = data.img |     captchaImgBase64.value = data.img | ||||||
|   } |   } | ||||||
|   onMounted(() => { |   onMounted(() => { | ||||||
|     getCaptcha() |     getCaptcha(); | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   // 记住我 |   // 记住我 | ||||||
|   | |||||||
| @@ -0,0 +1,142 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="container"> | ||||||
|  |     <a-table | ||||||
|  |       row-key="id" | ||||||
|  |       :loading="loading" | ||||||
|  |       :pagination="pagination" | ||||||
|  |       :columns="columns" | ||||||
|  |       :data="renderData" | ||||||
|  |       :bordered="false" | ||||||
|  |       size="large" | ||||||
|  |       @page-change="onPageChange" | ||||||
|  |     > | ||||||
|  |       <template #index="{ rowIndex }"> | ||||||
|  |         {{ rowIndex + 1 + (pagination.current - 1) * pagination.pageSize }} | ||||||
|  |       </template> | ||||||
|  |       <template #result="{ record }"> | ||||||
|  |         <a-space v-if="record.result === 1"> | ||||||
|  |           <a-tag color="green"> | ||||||
|  |             <span class="circle pass"></span> | ||||||
|  |             成功 | ||||||
|  |           </a-tag> | ||||||
|  |         </a-space> | ||||||
|  |         <a-space v-else> | ||||||
|  |           <a-tag color="red"> | ||||||
|  |             <span class="circle fail"></span> | ||||||
|  |             失败 | ||||||
|  |           </a-tag> | ||||||
|  |         </a-space> | ||||||
|  |       </template> | ||||||
|  |       <template #pagination-left> | ||||||
|  |         <a-tooltip content="刷新"> | ||||||
|  |           <div class="action-icon" @click="onRefresh"> | ||||||
|  |             <icon-refresh size="18" /> | ||||||
|  |           </div> | ||||||
|  |         </a-tooltip> | ||||||
|  |       </template> | ||||||
|  |     </a-table> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts" setup> | ||||||
|  |   import { computed, ref, reactive } from "vue"; | ||||||
|  |   import { useLoginStore } from '@/store'; | ||||||
|  |   import useLoading from '@/hooks/loading'; | ||||||
|  |   import { queryOperationLogList, OperationLogRecord, OperationLogParams } from '@/api/monitor/operation-log'; | ||||||
|  |   import { Pagination } from '@/types/global'; | ||||||
|  |   import type { TableColumnData } from '@arco-design/web-vue/es/table/interface'; | ||||||
|  |  | ||||||
|  |   const { loading, setLoading } = useLoading(true); | ||||||
|  |   const loginStore = useLoginStore(); | ||||||
|  |   const renderData = ref<OperationLogRecord[]>([]); | ||||||
|  |  | ||||||
|  |   const basePagination: Pagination = { | ||||||
|  |     current: 1, | ||||||
|  |     pageSize: 10, | ||||||
|  |   }; | ||||||
|  |   const pagination = reactive({ | ||||||
|  |     ...basePagination, | ||||||
|  |   }); | ||||||
|  |   const columns = computed<TableColumnData[]>(() => [ | ||||||
|  |     { | ||||||
|  |       title: '序号', | ||||||
|  |       dataIndex: 'index', | ||||||
|  |       slotName: 'index', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作时间', | ||||||
|  |       dataIndex: 'createTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作内容', | ||||||
|  |       dataIndex: 'description', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作结果', | ||||||
|  |       dataIndex: 'result', | ||||||
|  |       slotName: 'result', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作IP', | ||||||
|  |       dataIndex: 'requestIp', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作地点', | ||||||
|  |       dataIndex: 'location', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '浏览器', | ||||||
|  |       dataIndex: 'browser', | ||||||
|  |     }, | ||||||
|  |   ]); | ||||||
|  |   const fetchData = async ( | ||||||
|  |     params: OperationLogParams = { uid: loginStore.userId, page: 1, size: 10, sort: ['createTime,desc'] } | ||||||
|  |   ) => { | ||||||
|  |     setLoading(true); | ||||||
|  |     try { | ||||||
|  |       const { data } = await queryOperationLogList(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({ uid: loginStore.userId, page: current, size: pagination.pageSize, sort: ['createTime,desc'] }); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   const onRefresh = () => { | ||||||
|  |     fetchData({ | ||||||
|  |       uid: loginStore.userId, | ||||||
|  |       page: pagination.current, | ||||||
|  |       size: pagination.pageSize, | ||||||
|  |       sort: ['createTime,desc'], | ||||||
|  |     } as unknown as OperationLogParams); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|  |   fetchData(); | ||||||
|  | </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; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   .action-icon { | ||||||
|  |     cursor: pointer; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .action-icon:hover { | ||||||
|  |     color: #0960bd; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
| @@ -15,6 +15,9 @@ | |||||||
|           <a-tab-pane key="2" :title="$t('userCenter.tab.securitySettings')"> |           <a-tab-pane key="2" :title="$t('userCenter.tab.securitySettings')"> | ||||||
|             <SecuritySettings /> |             <SecuritySettings /> | ||||||
|           </a-tab-pane> |           </a-tab-pane> | ||||||
|  |           <a-tab-pane key="3" :title="$t('userCenter.tab.operationLog')"> | ||||||
|  |             <OperationLog /> | ||||||
|  |           </a-tab-pane> | ||||||
|         </a-tabs> |         </a-tabs> | ||||||
|       </a-col> |       </a-col> | ||||||
|     </a-row> |     </a-row> | ||||||
| @@ -25,6 +28,7 @@ | |||||||
|   import UserPanel from './components/user-panel.vue'; |   import UserPanel from './components/user-panel.vue'; | ||||||
|   import BasicInfo from './components/basic-info.vue'; |   import BasicInfo from './components/basic-info.vue'; | ||||||
|   import SecuritySettings from './components/security-settings.vue'; |   import SecuritySettings from './components/security-settings.vue'; | ||||||
|  |   import OperationLog from './components/operation-log.vue'; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ export default { | |||||||
|   'menu.user.center': 'User Center', |   'menu.user.center': 'User Center', | ||||||
|   'userCenter.tab.basicInfo': 'Basic Information', |   'userCenter.tab.basicInfo': 'Basic Information', | ||||||
|   'userCenter.tab.securitySettings': 'Security Settings', |   'userCenter.tab.securitySettings': 'Security Settings', | ||||||
|  |   'userCenter.tab.operationLog': 'Operation Log', | ||||||
|  |  | ||||||
|   // user-panel |   // user-panel | ||||||
|   'userCenter.panel.avatar': 'Avatar', |   'userCenter.panel.avatar': 'Avatar', | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ export default { | |||||||
|   'menu.user.center': '个人中心', |   'menu.user.center': '个人中心', | ||||||
|   'userCenter.tab.basicInfo': '基础信息', |   'userCenter.tab.basicInfo': '基础信息', | ||||||
|   'userCenter.tab.securitySettings': '安全设置', |   'userCenter.tab.securitySettings': '安全设置', | ||||||
|  |   'userCenter.tab.operationLog': '操作日志', | ||||||
|  |  | ||||||
|   // user-panel |   // user-panel | ||||||
|   'userCenter.panel.avatar': '头像', |   'userCenter.panel.avatar': '头像', | ||||||
|   | |||||||
| @@ -49,12 +49,6 @@ limitations under the License. | |||||||
|             <groupId>top.charles7c</groupId> |             <groupId>top.charles7c</groupId> | ||||||
|             <artifactId>continew-admin-monitor</artifactId> |             <artifactId>continew-admin-monitor</artifactId> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |  | ||||||
|         <!-- 系统管理模块(存放系统管理模块相关功能,例如:部门管理、角色管理、用户管理等) --> |  | ||||||
|         <dependency> |  | ||||||
|             <groupId>top.charles7c</groupId> |  | ||||||
|             <artifactId>continew-admin-system</artifactId> |  | ||||||
|         </dependency> |  | ||||||
|     </dependencies> |     </dependencies> | ||||||
|  |  | ||||||
|     <build> |     <build> | ||||||
|   | |||||||
| @@ -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.OperationLogQuery; | ||||||
|  | import top.charles7c.cnadmin.monitor.model.vo.OperationLogVO; | ||||||
|  | import top.charles7c.cnadmin.monitor.service.OperationLogService; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 操作日志 API | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/1/14 18:09 | ||||||
|  |  */ | ||||||
|  | @Tag(name = "操作日志 API") | ||||||
|  | @Validated | ||||||
|  | @RestController | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | @RequestMapping(value = "/monitor/log/operation", produces = MediaType.APPLICATION_JSON_VALUE) | ||||||
|  | public class OperationLogController { | ||||||
|  |  | ||||||
|  |     private final OperationLogService operationLogService; | ||||||
|  |  | ||||||
|  |     @Operation(summary = "分页查询操作日志列表") | ||||||
|  |     @GetMapping | ||||||
|  |     public R<PageInfo<OperationLogVO>> list(@Validated OperationLogQuery query, @Validated PageQuery pageQuery) { | ||||||
|  |         PageInfo<OperationLogVO> pageInfo = operationLogService.list(query, pageQuery); | ||||||
|  |         return R.ok(pageInfo); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -20,7 +20,7 @@ continew-admin: | |||||||
|     name: Apache-2.0 |     name: Apache-2.0 | ||||||
|     url: https://github.com/Charles7c/continew-admin/blob/dev/LICENSE |     url: https://github.com/Charles7c/continew-admin/blob/dev/LICENSE | ||||||
|   # 是否本地解析 IP 归属地 |   # 是否本地解析 IP 归属地 | ||||||
|   ipAddrLocalParseEnabled: false |   ipAddrLocalParseEnabled: true | ||||||
|  |  | ||||||
| --- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置) | --- ### 日志配置(重叠部分,优先级高于 logback-spring.xml 中的配置) | ||||||
| logging: | logging: | ||||||
| @@ -29,11 +29,13 @@ logging: | |||||||
|   file: |   file: | ||||||
|     path: @logging.file.path@ |     path: @logging.file.path@ | ||||||
|   config: classpath:logback-spring.xml |   config: classpath:logback-spring.xml | ||||||
|   ## 操作日志配置 |   ## 系统日志配置 | ||||||
|   operation: |   system: | ||||||
|     # 是否启用操作日志 |     # 是否启用系统日志 | ||||||
|     enabled: true |     enabled: true | ||||||
|     # 不记录操作日志的请求方式 |     # 是否记录内网 IP 操作 | ||||||
|  |     includeInnerIp: false | ||||||
|  |     # 哪些请求方式不记录系统日志 | ||||||
|     #excludeMethods: |     #excludeMethods: | ||||||
|     #  - GET |     #  - GET | ||||||
|     # 脱敏字段 |     # 脱敏字段 | ||||||
|   | |||||||
| @@ -26,7 +26,6 @@ CREATE TABLE IF NOT EXISTS `sys_user`  ( | |||||||
|  |  | ||||||
| CREATE TABLE IF NOT EXISTS `sys_log` ( | CREATE TABLE IF NOT EXISTS `sys_log` ( | ||||||
|     `log_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '日志ID', |     `log_id` bigint(20) unsigned AUTO_INCREMENT COMMENT '日志ID', | ||||||
|     `log_level` varchar(255) DEFAULT NULL COMMENT '日志级别', |  | ||||||
|     `description` varchar(255) DEFAULT NULL COMMENT '日志描述', |     `description` varchar(255) DEFAULT NULL COMMENT '日志描述', | ||||||
|     `request_url` varchar(512) NOT NULL DEFAULT '' COMMENT '请求URL', |     `request_url` varchar(512) NOT NULL DEFAULT '' COMMENT '请求URL', | ||||||
|     `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', |     `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式', | ||||||
| @@ -34,14 +33,15 @@ CREATE TABLE IF NOT EXISTS `sys_log` ( | |||||||
|     `request_body` text DEFAULT NULL COMMENT '请求体', |     `request_body` text DEFAULT NULL COMMENT '请求体', | ||||||
|     `status_code` int(11) unsigned DEFAULT NULL COMMENT '状态码', |     `status_code` int(11) unsigned DEFAULT NULL COMMENT '状态码', | ||||||
|     `response_header` text DEFAULT NULL COMMENT '响应头', |     `response_header` text DEFAULT NULL COMMENT '响应头', | ||||||
|     `response_body` text DEFAULT NULL COMMENT '响应体', |     `response_body` mediumtext DEFAULT NULL COMMENT '响应体', | ||||||
|     `elapsed_time` bigint(20) unsigned DEFAULT NULL COMMENT '请求耗时(ms)', |     `elapsed_time` bigint(20) unsigned DEFAULT NULL COMMENT '请求耗时(ms)', | ||||||
|     `request_ip` varchar(255) DEFAULT NULL COMMENT '请求IP', |     `result` tinyint(1) unsigned DEFAULT 1 COMMENT '操作结果(1成功 2失败)', | ||||||
|     `location` varchar(512) DEFAULT NULL COMMENT '操作地址', |     `request_ip` varchar(255) DEFAULT NULL COMMENT '操作IP', | ||||||
|  |     `location` varchar(512) DEFAULT NULL COMMENT '操作地点', | ||||||
|     `browser` varchar(255) DEFAULT NULL COMMENT '浏览器', |     `browser` varchar(255) DEFAULT NULL COMMENT '浏览器', | ||||||
|     `exception` text DEFAULT NULL COMMENT '异常', |     `exception` mediumtext DEFAULT NULL COMMENT '异常', | ||||||
|     `create_user` bigint(20) unsigned DEFAULT NULL COMMENT '操作人', |     `create_user` bigint(20) unsigned DEFAULT NULL COMMENT '操作人', | ||||||
|     `create_time` datetime NOT NULL COMMENT '操作时间', |     `create_time` datetime NOT NULL COMMENT '操作时间', | ||||||
|     PRIMARY KEY (`log_id`) USING BTREE, |     PRIMARY KEY (`log_id`) USING BTREE, | ||||||
|     INDEX `idx_createUser`(`create_user`) USING BTREE |     INDEX `idx_createUser`(`create_user`) USING BTREE | ||||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='操作日志表'; | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统日志表'; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user