Commit 132c1cc828334fa303ba53df5bc34ea82d8e5995
1 parent
9fac998a
【优化】增强访问日志,支持是否记录、脱敏、操作信息等功能
Showing
20 changed files
with
484 additions
and
297 deletions
sql/mysql/ruoyi-vue-pro.sql
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | Target Server Version : 80200 (8.2.0) |
| 12 | 12 | File Encoding : 65001 |
| 13 | 13 | |
| 14 | - Date: 30/03/2024 20:42:06 | |
| 14 | + Date: 03/04/2024 19:07:31 | |
| 15 | 15 | */ |
| 16 | 16 | |
| 17 | 17 | SET NAMES utf8mb4; |
| ... | ... | @@ -327,9 +327,13 @@ CREATE TABLE `infra_api_access_log` ( |
| 327 | 327 | `application_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '应用名', |
| 328 | 328 | `request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求方法名', |
| 329 | 329 | `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求地址', |
| 330 | - `request_params` varchar(8000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '请求参数', | |
| 330 | + `request_params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '请求参数', | |
| 331 | + `response_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '响应结果', | |
| 331 | 332 | `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户 IP', |
| 332 | 333 | `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '浏览器 UA', |
| 334 | + `operate_module` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作模块', | |
| 335 | + `operate_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '操作名', | |
| 336 | + `operate_type` tinyint NULL DEFAULT 0 COMMENT '操作分类', | |
| 333 | 337 | `begin_time` datetime NOT NULL COMMENT '开始请求时间', |
| 334 | 338 | `end_time` datetime NOT NULL COMMENT '结束请求时间', |
| 335 | 339 | `duration` int NOT NULL COMMENT '执行时长', |
| ... | ... | @@ -343,7 +347,7 @@ CREATE TABLE `infra_api_access_log` ( |
| 343 | 347 | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', |
| 344 | 348 | PRIMARY KEY (`id`) USING BTREE, |
| 345 | 349 | INDEX `idx_create_time`(`create_time` ASC) USING BTREE |
| 346 | -) ENGINE = InnoDB AUTO_INCREMENT = 35832 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; | |
| 350 | +) ENGINE = InnoDB AUTO_INCREMENT = 35920 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'API 访问日志表'; | |
| 347 | 351 | |
| 348 | 352 | -- ---------------------------- |
| 349 | 353 | -- Records of infra_api_access_log |
| ... | ... | @@ -385,7 +389,7 @@ CREATE TABLE `infra_api_error_log` ( |
| 385 | 389 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 386 | 390 | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', |
| 387 | 391 | PRIMARY KEY (`id`) USING BTREE |
| 388 | -) ENGINE = InnoDB AUTO_INCREMENT = 16429 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; | |
| 392 | +) ENGINE = InnoDB AUTO_INCREMENT = 16462 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志'; | |
| 389 | 393 | |
| 390 | 394 | -- ---------------------------- |
| 391 | 395 | -- Records of infra_api_error_log |
| ... | ... | @@ -494,7 +498,7 @@ CREATE TABLE `infra_config` ( |
| 494 | 498 | -- Records of infra_config |
| 495 | 499 | -- ---------------------------- |
| 496 | 500 | BEGIN; |
| 497 | -INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'sys.user.init-password', '123456', b'0', '初始化密码 123456', 'admin', '2021-01-05 17:03:48', '1', '2024-02-28 22:54:14', b'0'); | |
| 501 | +INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2, 'biz', 1, '用户管理-账号初始密码', 'sys.user.init-password', '123456', b'0', '初始化密码 123456', 'admin', '2021-01-05 17:03:48', '1', '2024-04-03 17:22:28', b'0'); | |
| 498 | 502 | INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (7, 'url', 2, 'MySQL 监控的地址', 'url.druid', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:33:38', b'0'); |
| 499 | 503 | INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (8, 'url', 2, 'SkyWalking 监控的地址', 'url.skywalking', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:57:03', b'0'); |
| 500 | 504 | INSERT INTO `infra_config` (`id`, `category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (9, 'url', 2, 'Spring Boot Admin 监控的地址', 'url.spring-boot-admin', '', b'1', '', '1', '2023-04-07 13:41:16', '1', '2023-04-07 14:52:07', b'0'); |
| ... | ... | @@ -690,7 +694,7 @@ CREATE TABLE `infra_file` ( |
| 690 | 694 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
| 691 | 695 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 692 | 696 | PRIMARY KEY (`id`) USING BTREE |
| 693 | -) ENGINE = InnoDB AUTO_INCREMENT = 1301 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; | |
| 697 | +) ENGINE = InnoDB AUTO_INCREMENT = 1302 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表'; | |
| 694 | 698 | |
| 695 | 699 | -- ---------------------------- |
| 696 | 700 | -- Records of infra_file |
| ... | ... | @@ -1416,7 +1420,7 @@ CREATE TABLE `system_login_log` ( |
| 1416 | 1420 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 1417 | 1421 | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', |
| 1418 | 1422 | PRIMARY KEY (`id`) USING BTREE |
| 1419 | -) ENGINE = InnoDB AUTO_INCREMENT = 3054 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; | |
| 1423 | +) ENGINE = InnoDB AUTO_INCREMENT = 3066 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录'; | |
| 1420 | 1424 | |
| 1421 | 1425 | -- ---------------------------- |
| 1422 | 1426 | -- Records of system_login_log |
| ... | ... | @@ -2453,7 +2457,7 @@ CREATE TABLE `system_oauth2_access_token` ( |
| 2453 | 2457 | PRIMARY KEY (`id`) USING BTREE, |
| 2454 | 2458 | INDEX `idx_access_token`(`access_token` ASC) USING BTREE, |
| 2455 | 2459 | INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE |
| 2456 | -) ENGINE = InnoDB AUTO_INCREMENT = 6332 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; | |
| 2460 | +) ENGINE = InnoDB AUTO_INCREMENT = 6366 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌'; | |
| 2457 | 2461 | |
| 2458 | 2462 | -- ---------------------------- |
| 2459 | 2463 | -- Records of system_oauth2_access_token |
| ... | ... | @@ -2575,7 +2579,7 @@ CREATE TABLE `system_oauth2_refresh_token` ( |
| 2575 | 2579 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 2576 | 2580 | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', |
| 2577 | 2581 | PRIMARY KEY (`id`) USING BTREE |
| 2578 | -) ENGINE = InnoDB AUTO_INCREMENT = 1430 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; | |
| 2582 | +) ENGINE = InnoDB AUTO_INCREMENT = 1441 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌'; | |
| 2579 | 2583 | |
| 2580 | 2584 | -- ---------------------------- |
| 2581 | 2585 | -- Records of system_oauth2_refresh_token |
| ... | ... | @@ -2615,7 +2619,7 @@ CREATE TABLE `system_operate_log` ( |
| 2615 | 2619 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 2616 | 2620 | `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', |
| 2617 | 2621 | PRIMARY KEY (`id`) USING BTREE |
| 2618 | -) ENGINE = InnoDB AUTO_INCREMENT = 11964 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录'; | |
| 2622 | +) ENGINE = InnoDB AUTO_INCREMENT = 12000 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '操作日志记录'; | |
| 2619 | 2623 | |
| 2620 | 2624 | -- ---------------------------- |
| 2621 | 2625 | -- Records of system_operate_log |
| ... | ... | @@ -5305,7 +5309,7 @@ CREATE TABLE `system_sms_log` ( |
| 5305 | 5309 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
| 5306 | 5310 | `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', |
| 5307 | 5311 | PRIMARY KEY (`id`) USING BTREE |
| 5308 | -) ENGINE = InnoDB AUTO_INCREMENT = 946 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; | |
| 5312 | +) ENGINE = InnoDB AUTO_INCREMENT = 947 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志'; | |
| 5309 | 5313 | |
| 5310 | 5314 | -- ---------------------------- |
| 5311 | 5315 | -- Records of system_sms_log |
| ... | ... | @@ -5475,7 +5479,7 @@ INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `c |
| 5475 | 5479 | INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (151, '大租户', 126, '土豆大', NULL, 0, 'https://tudou.iocoder.cn', 111, '2023-12-08 00:00:00', 10, '1', '2023-12-02 23:35:05', '1', '2023-12-08 23:39:56', b'0'); |
| 5476 | 5480 | INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (152, '新租户', 127, '土豆', NULL, 0, 'http://xx.iocoder.cn', 111, '2025-12-31 00:00:00', 50, '1', '2023-12-30 11:43:17', '1', '2023-12-30 11:43:17', b'0'); |
| 5477 | 5481 | INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (153, '小明的租户', 128, 'xiaoming', '15601691301', 0, 'xiaoming.iocoder.cn', 111, '2025-12-01 00:00:00', 100, '1', '2024-02-27 21:58:25', '1', '2024-02-28 22:53:54', b'0'); |
| 5478 | -INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (154, 'hh', 129, 'hh', NULL, 0, 'http://hh.iocoder.cn', 111, '2024-04-30 00:00:00', 123, '1', '2024-03-30 17:52:59', '1', '2024-03-30 17:52:59', b'0'); | |
| 5482 | +INSERT INTO `system_tenant` (`id`, `name`, `contact_user_id`, `contact_name`, `contact_mobile`, `status`, `website`, `package_id`, `expire_time`, `account_count`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (154, 'hh', 129, 'hh', NULL, 0, 'http://hh.iocoder.cn', 111, '2024-04-30 00:00:00', 123, '1', '2024-03-30 17:52:59', '1', '2024-04-03 15:06:42', b'0'); | |
| 5479 | 5483 | COMMIT; |
| 5480 | 5484 | |
| 5481 | 5485 | -- ---------------------------- |
| ... | ... | @@ -5619,7 +5623,7 @@ CREATE TABLE `system_users` ( |
| 5619 | 5623 | -- Records of system_users |
| 5620 | 5624 | -- ---------------------------- |
| 5621 | 5625 | BEGIN; |
| 5622 | -INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '0:0:0:0:0:0:0:1', '2024-03-30 17:18:34', 'admin', '2021-01-05 17:03:47', NULL, '2024-03-30 17:18:34', b'0', 1); | |
| 5626 | +INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/96c787a2ce88bf6d0ce3cd8b6cf5314e80e7703cd41bf4af8cd2e2909dbd6b6d.png', 0, '127.0.0.1', '2024-04-03 17:31:00', 'admin', '2021-01-05 17:03:47', NULL, '2024-04-03 17:31:00', b'0', 1); | |
| 5623 | 5627 | INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$10$11U48RhyJ5pSBYWSn12AD./ld671.ycSzJHbyrtpeoMeYiw31eo8a', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 1, '127.0.0.1', '2022-07-09 23:03:33', '', '2021-01-07 09:07:17', NULL, '2022-07-09 23:03:33', b'0', 1); |
| 5624 | 5628 | INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$10$YMpimV4T6BtDhIaA8jSW.u8UTGBeGhc/qwXP4oxoMr4mOw9.qttt6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-03-18 21:09:04', '', '2021-01-13 23:50:35', NULL, '2024-03-18 21:09:04', b'0', 1); |
| 5625 | 5629 | INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$KhExCYl7lx6eWWZYKsibKOZ8IBJRyuNuCcEOLQ11RYhJKgHmlSwK.', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-03-26 07:11:35', '', '2021-01-21 02:13:53', NULL, '2024-03-26 07:11:35', b'0', 1); | ... | ... |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringAopUtils.java deleted
| 1 | -package cn.iocoder.yudao.framework.common.util.spring; | |
| 2 | - | |
| 3 | -import cn.hutool.core.bean.BeanUtil; | |
| 4 | -import org.springframework.aop.framework.AdvisedSupport; | |
| 5 | -import org.springframework.aop.framework.AopProxy; | |
| 6 | -import org.springframework.aop.support.AopUtils; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * Spring AOP 工具类 | |
| 10 | - * | |
| 11 | - * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现 | |
| 12 | - */ | |
| 13 | -public class SpringAopUtils { | |
| 14 | - | |
| 15 | - /** | |
| 16 | - * 获取代理的目标对象 | |
| 17 | - * | |
| 18 | - * @param proxy 代理对象 | |
| 19 | - * @return 目标对象 | |
| 20 | - */ | |
| 21 | - public static Object getTarget(Object proxy) throws Exception { | |
| 22 | - // 不是代理对象 | |
| 23 | - if (!AopUtils.isAopProxy(proxy)) { | |
| 24 | - return proxy; | |
| 25 | - } | |
| 26 | - // Jdk 代理 | |
| 27 | - if (AopUtils.isJdkDynamicProxy(proxy)) { | |
| 28 | - return getJdkDynamicProxyTargetObject(proxy); | |
| 29 | - } | |
| 30 | - // Cglib 代理 | |
| 31 | - return getCglibProxyTargetObject(proxy); | |
| 32 | - } | |
| 33 | - | |
| 34 | - private static Object getCglibProxyTargetObject(Object proxy) throws Exception { | |
| 35 | - Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0"); | |
| 36 | - AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised"); | |
| 37 | - return advisedSupport.getTargetSource().getTarget(); | |
| 38 | - } | |
| 39 | - | |
| 40 | - private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception { | |
| 41 | - AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h"); | |
| 42 | - AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised"); | |
| 43 | - return advisedSupport.getTargetSource().getTarget(); | |
| 44 | - } | |
| 45 | - | |
| 46 | -} |
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/spring/SpringUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.common.util.spring; | |
| 2 | + | |
| 3 | +import cn.hutool.extra.spring.SpringUtil; | |
| 4 | + | |
| 5 | +import java.util.Objects; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * Spring 工具类 | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +public class SpringUtils extends SpringUtil { | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 是否为生产环境 | |
| 16 | + * | |
| 17 | + * @return 是否生产环境 | |
| 18 | + */ | |
| 19 | + public static boolean isProd() { | |
| 20 | + String activeProfile = getActiveProfile(); | |
| 21 | + return Objects.equals("prod", activeProfile); | |
| 22 | + } | |
| 23 | + | |
| 24 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/config/YudaoApiLogAutoConfiguration.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.config; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.apilog.core.filter.ApiAccessLogFilter; |
| 4 | +import cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor; | |
| 4 | 5 | import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService; |
| 5 | 6 | import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl; |
| 6 | 7 | import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService; |
| ... | ... | @@ -10,23 +11,26 @@ import cn.iocoder.yudao.framework.web.config.WebProperties; |
| 10 | 11 | import cn.iocoder.yudao.framework.web.config.YudaoWebAutoConfiguration; |
| 11 | 12 | import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi; |
| 12 | 13 | import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi; |
| 14 | +import jakarta.servlet.Filter; | |
| 13 | 15 | import org.springframework.beans.factory.annotation.Value; |
| 14 | 16 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| 15 | 17 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
| 16 | 18 | import org.springframework.boot.web.servlet.FilterRegistrationBean; |
| 17 | 19 | import org.springframework.context.annotation.Bean; |
| 18 | - | |
| 19 | -import jakarta.servlet.Filter; | |
| 20 | +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | |
| 21 | +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | |
| 20 | 22 | |
| 21 | 23 | @AutoConfiguration(after = YudaoWebAutoConfiguration.class) |
| 22 | -public class YudaoApiLogAutoConfiguration { | |
| 24 | +public class YudaoApiLogAutoConfiguration implements WebMvcConfigurer { | |
| 23 | 25 | |
| 24 | 26 | @Bean |
| 27 | + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | |
| 25 | 28 | public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) { |
| 26 | 29 | return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi); |
| 27 | 30 | } |
| 28 | 31 | |
| 29 | 32 | @Bean |
| 33 | + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | |
| 30 | 34 | public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) { |
| 31 | 35 | return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi); |
| 32 | 36 | } |
| ... | ... | @@ -49,4 +53,9 @@ public class YudaoApiLogAutoConfiguration { |
| 49 | 53 | return bean; |
| 50 | 54 | } |
| 51 | 55 | |
| 56 | + @Override | |
| 57 | + public void addInterceptors(InterceptorRegistry registry) { | |
| 58 | + registry.addInterceptor(new ApiAccessLogInterceptor()); | |
| 59 | + } | |
| 60 | + | |
| 52 | 61 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/annotations/ApiAccessLog.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.apilog.core.annotations; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum; | |
| 4 | + | |
| 5 | +import java.lang.annotation.ElementType; | |
| 6 | +import java.lang.annotation.Retention; | |
| 7 | +import java.lang.annotation.RetentionPolicy; | |
| 8 | +import java.lang.annotation.Target; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 访问日志注解 | |
| 12 | + * | |
| 13 | + * @author 芋道源码 | |
| 14 | + */ | |
| 15 | +@Target({ElementType.METHOD}) | |
| 16 | +@Retention(RetentionPolicy.RUNTIME) | |
| 17 | +public @interface ApiAccessLog { | |
| 18 | + | |
| 19 | + // ========== 开关字段 ========== | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * 是否记录访问日志 | |
| 23 | + */ | |
| 24 | + boolean enable() default true; | |
| 25 | + /** | |
| 26 | + * 是否记录请求参数 | |
| 27 | + * | |
| 28 | + * 默认记录,主要考虑请求数据一般不大。可手动设置为 false 进行关闭 | |
| 29 | + */ | |
| 30 | + boolean requestEnable() default true; | |
| 31 | + /** | |
| 32 | + * 是否记录响应结果 | |
| 33 | + * | |
| 34 | + * 默认不记录,主要考虑响应数据可能比较大。可手动设置为 true 进行打开 | |
| 35 | + */ | |
| 36 | + boolean responseEnable() default false; | |
| 37 | + /** | |
| 38 | + * 敏感参数数组 | |
| 39 | + * | |
| 40 | + * 添加后,请求参数、响应结果不会记录该参数 | |
| 41 | + */ | |
| 42 | + String[] sanitizeKeys() default {}; | |
| 43 | + | |
| 44 | + // ========== 模块字段 ========== | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 操作模块 | |
| 48 | + * | |
| 49 | + * 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性 | |
| 50 | + */ | |
| 51 | + String operateModule() default ""; | |
| 52 | + /** | |
| 53 | + * 操作名 | |
| 54 | + * | |
| 55 | + * 为空时,会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性 | |
| 56 | + */ | |
| 57 | + String operateName() default ""; | |
| 58 | + /** | |
| 59 | + * 操作分类 | |
| 60 | + * | |
| 61 | + * 实际并不是数组,因为枚举不能设置 null 作为默认值 | |
| 62 | + */ | |
| 63 | + OperateTypeEnum[] operateType() default {}; | |
| 64 | + | |
| 65 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/enums/OperateTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.apilog.core.enums; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Getter; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 操作日志的操作类型 | |
| 8 | + * | |
| 9 | + * @author ruoyi | |
| 10 | + */ | |
| 11 | +@Getter | |
| 12 | +@AllArgsConstructor | |
| 13 | +public enum OperateTypeEnum { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 查询 | |
| 17 | + */ | |
| 18 | + GET(1), | |
| 19 | + /** | |
| 20 | + * 新增 | |
| 21 | + */ | |
| 22 | + CREATE(2), | |
| 23 | + /** | |
| 24 | + * 修改 | |
| 25 | + */ | |
| 26 | + UPDATE(3), | |
| 27 | + /** | |
| 28 | + * 删除 | |
| 29 | + */ | |
| 30 | + DELETE(4), | |
| 31 | + /** | |
| 32 | + * 导出 | |
| 33 | + */ | |
| 34 | + EXPORT(5), | |
| 35 | + /** | |
| 36 | + * 导入 | |
| 37 | + */ | |
| 38 | + IMPORT(6), | |
| 39 | + /** | |
| 40 | + * 其它 | |
| 41 | + * | |
| 42 | + * 在无法归类时,可以选择使用其它。因为还有操作名可以进一步标识 | |
| 43 | + */ | |
| 44 | + OTHER(0); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 类型 | |
| 48 | + */ | |
| 49 | + private final Integer type; | |
| 50 | + | |
| 51 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/filter/ApiAccessLogFilter.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.core.filter; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.hutool.core.date.LocalDateTimeUtil; |
| 4 | 5 | import cn.hutool.core.exceptions.ExceptionUtil; |
| 5 | 6 | import cn.hutool.core.map.MapUtil; |
| 6 | -import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLog; | |
| 7 | +import cn.hutool.core.util.ArrayUtil; | |
| 8 | +import cn.hutool.core.util.BooleanUtil; | |
| 9 | +import cn.hutool.core.util.StrUtil; | |
| 10 | +import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog; | |
| 11 | +import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum; | |
| 7 | 12 | import cn.iocoder.yudao.framework.apilog.core.service.ApiAccessLogFrameworkService; |
| 8 | 13 | import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; |
| 9 | 14 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 15 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 10 | 16 | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; |
| 11 | 17 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| 12 | 18 | import cn.iocoder.yudao.framework.web.config.WebProperties; |
| 13 | 19 | import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; |
| 14 | 20 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 15 | -import lombok.extern.slf4j.Slf4j; | |
| 16 | - | |
| 21 | +import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; | |
| 22 | +import com.fasterxml.jackson.databind.JsonNode; | |
| 23 | +import io.swagger.v3.oas.annotations.Operation; | |
| 24 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 17 | 25 | import jakarta.servlet.FilterChain; |
| 18 | 26 | import jakarta.servlet.ServletException; |
| 19 | 27 | import jakarta.servlet.http.HttpServletRequest; |
| 20 | 28 | import jakarta.servlet.http.HttpServletResponse; |
| 29 | +import lombok.extern.slf4j.Slf4j; | |
| 30 | +import org.springframework.web.bind.annotation.RequestMethod; | |
| 31 | +import org.springframework.web.method.HandlerMethod; | |
| 32 | + | |
| 21 | 33 | import java.io.IOException; |
| 22 | 34 | import java.time.LocalDateTime; |
| 23 | 35 | import java.time.temporal.ChronoUnit; |
| 36 | +import java.util.Iterator; | |
| 24 | 37 | import java.util.Map; |
| 25 | 38 | |
| 39 | +import static cn.iocoder.yudao.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*; | |
| 26 | 40 | import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |
| 27 | 41 | |
| 28 | 42 | /** |
| 29 | 43 | * API 访问日志 Filter |
| 30 | 44 | * |
| 45 | + * 目的:记录 API 访问日志到数据库中 | |
| 46 | + * | |
| 31 | 47 | * @author 芋道源码 |
| 32 | 48 | */ |
| 33 | 49 | @Slf4j |
| 34 | 50 | public class ApiAccessLogFilter extends ApiRequestFilter { |
| 35 | 51 | |
| 52 | + private static final String[] SANITIZE_KEYS = new String[]{"password", "token", "accessToken", "refreshToken"}; | |
| 53 | + | |
| 36 | 54 | private final String applicationName; |
| 37 | 55 | |
| 38 | 56 | private final ApiAccessLogFrameworkService apiAccessLogFrameworkService; |
| ... | ... | @@ -44,6 +62,7 @@ public class ApiAccessLogFilter extends ApiRequestFilter { |
| 44 | 62 | } |
| 45 | 63 | |
| 46 | 64 | @Override |
| 65 | + @SuppressWarnings("NullableProblems") | |
| 47 | 66 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) |
| 48 | 67 | throws ServletException, IOException { |
| 49 | 68 | // 获得开始时间 |
| ... | ... | @@ -66,45 +85,166 @@ public class ApiAccessLogFilter extends ApiRequestFilter { |
| 66 | 85 | |
| 67 | 86 | private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime, |
| 68 | 87 | Map<String, String> queryString, String requestBody, Exception ex) { |
| 69 | - ApiAccessLog accessLog = new ApiAccessLog(); | |
| 88 | + ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO(); | |
| 70 | 89 | try { |
| 71 | - this.buildApiAccessLogDTO(accessLog, request, beginTime, queryString, requestBody, ex); | |
| 90 | + boolean enable = buildApiAccessLog(accessLog, request, beginTime, queryString, requestBody, ex); | |
| 91 | + if (!enable) { | |
| 92 | + return; | |
| 93 | + } | |
| 72 | 94 | apiAccessLogFrameworkService.createApiAccessLog(accessLog); |
| 73 | 95 | } catch (Throwable th) { |
| 74 | 96 | log.error("[createApiAccessLog][url({}) log({}) 发生异常]", request.getRequestURI(), toJsonString(accessLog), th); |
| 75 | 97 | } |
| 76 | 98 | } |
| 77 | 99 | |
| 78 | - private void buildApiAccessLogDTO(ApiAccessLog accessLog, HttpServletRequest request, LocalDateTime beginTime, | |
| 100 | + private boolean buildApiAccessLog(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, LocalDateTime beginTime, | |
| 79 | 101 | Map<String, String> queryString, String requestBody, Exception ex) { |
| 102 | + // 判断:是否要记录操作日志 | |
| 103 | + HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD); | |
| 104 | + ApiAccessLog accessLogAnnotation = null; | |
| 105 | + if (handlerMethod != null) { | |
| 106 | + accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class); | |
| 107 | + if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) { | |
| 108 | + return false; | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 80 | 112 | // 处理用户信息 |
| 81 | - accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); | |
| 82 | - accessLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); | |
| 113 | + accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request)) | |
| 114 | + .setUserType(WebFrameworkUtils.getLoginUserType(request)); | |
| 83 | 115 | // 设置访问结果 |
| 84 | 116 | CommonResult<?> result = WebFrameworkUtils.getCommonResult(request); |
| 85 | 117 | if (result != null) { |
| 86 | - accessLog.setResultCode(result.getCode()); | |
| 87 | - accessLog.setResultMsg(result.getMsg()); | |
| 118 | + accessLog.setResultCode(result.getCode()).setResultMsg(result.getMsg()); | |
| 88 | 119 | } else if (ex != null) { |
| 89 | - accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()); | |
| 90 | - accessLog.setResultMsg(ExceptionUtil.getRootCauseMessage(ex)); | |
| 120 | + accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode()) | |
| 121 | + .setResultMsg(ExceptionUtil.getRootCauseMessage(ex)); | |
| 91 | 122 | } else { |
| 92 | - accessLog.setResultCode(0); | |
| 93 | - accessLog.setResultMsg(""); | |
| 94 | - } | |
| 95 | - // 设置其它字段 | |
| 96 | - accessLog.setTraceId(TracerUtils.getTraceId()); | |
| 97 | - accessLog.setApplicationName(applicationName); | |
| 98 | - accessLog.setRequestUrl(request.getRequestURI()); | |
| 99 | - Map<String, Object> requestParams = MapUtil.<String, Object>builder().put("query", queryString).put("body", requestBody).build(); | |
| 100 | - accessLog.setRequestParams(toJsonString(requestParams)); | |
| 101 | - accessLog.setRequestMethod(request.getMethod()); | |
| 102 | - accessLog.setUserAgent(ServletUtils.getUserAgent(request)); | |
| 103 | - accessLog.setUserIp(ServletUtils.getClientIP(request)); | |
| 123 | + accessLog.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode()).setResultMsg(""); | |
| 124 | + } | |
| 125 | + // 设置请求字段 | |
| 126 | + accessLog.setTraceId(TracerUtils.getTraceId()).setApplicationName(applicationName) | |
| 127 | + .setRequestUrl(request.getRequestURI()).setRequestMethod(request.getMethod()) | |
| 128 | + .setUserAgent(ServletUtils.getUserAgent(request)).setUserIp(ServletUtils.getClientIP(request)); | |
| 129 | + String[] sanitizeKeys = accessLogAnnotation != null ? accessLogAnnotation.sanitizeKeys() : null; | |
| 130 | + Boolean requestEnable = accessLogAnnotation != null ? accessLogAnnotation.requestEnable() : Boolean.TRUE; | |
| 131 | + if (!BooleanUtil.isFalse(requestEnable)) { // 默认记录,所以判断 !false | |
| 132 | + Map<String, Object> requestParams = MapUtil.<String, Object>builder() | |
| 133 | + .put("query", sanitizeMap(queryString, sanitizeKeys)) | |
| 134 | + .put("body", sanitizeJson(requestBody, sanitizeKeys)).build(); | |
| 135 | + accessLog.setRequestParams(toJsonString(requestParams)); | |
| 136 | + } | |
| 137 | + Boolean responseEnable = accessLogAnnotation != null ? accessLogAnnotation.responseEnable() : Boolean.FALSE; | |
| 138 | + if (BooleanUtil.isTrue(responseEnable)) { // 默认不记录,默认强制要求 true | |
| 139 | + accessLog.setResponseBody(sanitizeJson(result, sanitizeKeys)); | |
| 140 | + } | |
| 104 | 141 | // 持续时间 |
| 105 | - accessLog.setBeginTime(beginTime); | |
| 106 | - accessLog.setEndTime(LocalDateTime.now()); | |
| 107 | - accessLog.setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS)); | |
| 142 | + accessLog.setBeginTime(beginTime).setEndTime(LocalDateTime.now()) | |
| 143 | + .setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS)); | |
| 144 | + | |
| 145 | + // 操作模块 | |
| 146 | + if (handlerMethod != null) { | |
| 147 | + Tag tagAnnotation = handlerMethod.getBeanType().getAnnotation(Tag.class); | |
| 148 | + Operation operationAnnotation = handlerMethod.getMethodAnnotation(Operation.class); | |
| 149 | + String operateModule = accessLogAnnotation != null ? accessLogAnnotation.operateModule() : | |
| 150 | + tagAnnotation != null ? StrUtil.nullToDefault(tagAnnotation.name(), tagAnnotation.description()) : null; | |
| 151 | + String operateName = accessLogAnnotation != null ? accessLogAnnotation.operateName() : | |
| 152 | + operationAnnotation != null ? operationAnnotation.summary() : null; | |
| 153 | + OperateTypeEnum operateType = accessLogAnnotation != null && accessLogAnnotation.operateType().length > 0 ? | |
| 154 | + accessLogAnnotation.operateType()[0] : parseOperateLogType(request); | |
| 155 | + accessLog.setOperateModule(operateModule).setOperateName(operateName).setOperateType(operateType.getType()); | |
| 156 | + } | |
| 157 | + return true; | |
| 158 | + } | |
| 159 | + | |
| 160 | + // ========== 解析 @ApiAccessLog、@Swagger 注解 ========== | |
| 161 | + | |
| 162 | + private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) { | |
| 163 | + RequestMethod requestMethod = RequestMethod.resolve(request.getMethod()); | |
| 164 | + if (requestMethod == null) { | |
| 165 | + return OperateTypeEnum.OTHER; | |
| 166 | + } | |
| 167 | + switch (requestMethod) { | |
| 168 | + case GET: | |
| 169 | + return OperateTypeEnum.GET; | |
| 170 | + case POST: | |
| 171 | + return OperateTypeEnum.CREATE; | |
| 172 | + case PUT: | |
| 173 | + return OperateTypeEnum.UPDATE; | |
| 174 | + case DELETE: | |
| 175 | + return OperateTypeEnum.DELETE; | |
| 176 | + default: | |
| 177 | + return OperateTypeEnum.OTHER; | |
| 178 | + } | |
| 179 | + } | |
| 180 | + | |
| 181 | + // ========== 请求和响应的脱敏逻辑,移除类似 password、token 等敏感字段 ========== | |
| 182 | + | |
| 183 | + private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) { | |
| 184 | + if (CollUtil.isNotEmpty(map)) { | |
| 185 | + return null; | |
| 186 | + } | |
| 187 | + if (sanitizeKeys != null) { | |
| 188 | + MapUtil.removeAny(map, sanitizeKeys); | |
| 189 | + } | |
| 190 | + MapUtil.removeAny(map, SANITIZE_KEYS); | |
| 191 | + return JsonUtils.toJsonString(map); | |
| 192 | + } | |
| 193 | + | |
| 194 | + private static String sanitizeJson(String jsonString, String[] sanitizeKeys) { | |
| 195 | + if (StrUtil.isEmpty(jsonString)) { | |
| 196 | + return null; | |
| 197 | + } | |
| 198 | + try { | |
| 199 | + JsonNode rootNode = JsonUtils.parseTree(jsonString); | |
| 200 | + sanitizeJson(rootNode, sanitizeKeys); | |
| 201 | + return JsonUtils.toJsonString(rootNode); | |
| 202 | + } catch (Exception e) { | |
| 203 | + // 脱敏失败的情况下,直接忽略异常,避免影响用户请求 | |
| 204 | + log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e); | |
| 205 | + return jsonString; | |
| 206 | + } | |
| 207 | + } | |
| 208 | + | |
| 209 | + private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) { | |
| 210 | + if (commonResult == null) { | |
| 211 | + return null; | |
| 212 | + } | |
| 213 | + String jsonString = toJsonString(commonResult); | |
| 214 | + try { | |
| 215 | + JsonNode rootNode = JsonUtils.parseTree(jsonString); | |
| 216 | + sanitizeJson(rootNode.get("data"), sanitizeKeys); // 只处理 data 字段,不处理 code、msg 字段,避免错误被脱敏掉 | |
| 217 | + return JsonUtils.toJsonString(rootNode); | |
| 218 | + } catch (Exception e) { | |
| 219 | + // 脱敏失败的情况下,直接忽略异常,避免影响用户请求 | |
| 220 | + log.error("[sanitizeJson][脱敏({}) 发生异常]", jsonString, e); | |
| 221 | + return jsonString; | |
| 222 | + } | |
| 223 | + } | |
| 224 | + | |
| 225 | + private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) { | |
| 226 | + // 情况一:数组,遍历处理 | |
| 227 | + if (node.isArray()) { | |
| 228 | + for (JsonNode childNode : node) { | |
| 229 | + sanitizeJson(childNode, sanitizeKeys); | |
| 230 | + } | |
| 231 | + return; | |
| 232 | + } | |
| 233 | + // 情况二:非 Object,只是某个值,直接返回 | |
| 234 | + if (!node.isObject()) { | |
| 235 | + return; | |
| 236 | + } | |
| 237 | + // 情况三:Object,遍历处理 | |
| 238 | + Iterator<Map.Entry<String, JsonNode>> iterator = node.properties().iterator(); | |
| 239 | + while (iterator.hasNext()) { | |
| 240 | + Map.Entry<String, JsonNode> entry = iterator.next(); | |
| 241 | + if (ArrayUtil.contains(sanitizeKeys, entry.getKey()) | |
| 242 | + || ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) { | |
| 243 | + iterator.remove(); | |
| 244 | + continue; | |
| 245 | + } | |
| 246 | + sanitizeJson(entry.getValue(), sanitizeKeys); | |
| 247 | + } | |
| 108 | 248 | } |
| 109 | 249 | |
| 110 | 250 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/interceptor/ApiAccessLogInterceptor.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.apilog.core.interceptor; | |
| 2 | + | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; | |
| 6 | +import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; | |
| 7 | +import jakarta.servlet.http.HttpServletRequest; | |
| 8 | +import jakarta.servlet.http.HttpServletResponse; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | +import org.springframework.util.StopWatch; | |
| 11 | +import org.springframework.web.method.HandlerMethod; | |
| 12 | +import org.springframework.web.servlet.HandlerInterceptor; | |
| 13 | + | |
| 14 | +import java.util.Map; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * API 访问日志 Interceptor | |
| 18 | + * | |
| 19 | + * 目的:在非 prod 环境时,打印 request 和 response 两条日志到日志文件(控制台)中。 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +@Slf4j | |
| 24 | +public class ApiAccessLogInterceptor implements HandlerInterceptor { | |
| 25 | + | |
| 26 | + public static String ATTRIBUTE_HANDLER_METHOD = "HANDLER_METHOD"; | |
| 27 | + | |
| 28 | + private static String ATTRIBUTE_STOP_WATCH = "ApiAccessLogInterceptor.StopWatch"; | |
| 29 | + | |
| 30 | + @Override | |
| 31 | + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { | |
| 32 | + // 记录 HandlerMethod,提供给 ApiAccessLogFilter 使用 | |
| 33 | + HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null; | |
| 34 | + if (handlerMethod != null) { | |
| 35 | + request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod); | |
| 36 | + } | |
| 37 | + | |
| 38 | + // 打印 request 日志 | |
| 39 | + if (!SpringUtils.isProd()) { | |
| 40 | + Map<String, String> queryString = ServletUtils.getParamMap(request); | |
| 41 | + String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null; | |
| 42 | + if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) { | |
| 43 | + log.info("[preHandle][开始请求 URL({}) 无参数]", request.getRequestURI()); | |
| 44 | + } else { | |
| 45 | + log.info("[preHandle][开始请求 URL({}) 参数({})]", request.getRequestURI(), | |
| 46 | + StrUtil.nullToDefault(requestBody, queryString.toString())); | |
| 47 | + } | |
| 48 | + // 计时 | |
| 49 | + StopWatch stopWatch = new StopWatch(); | |
| 50 | + stopWatch.start(); | |
| 51 | + request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch); | |
| 52 | + } | |
| 53 | + return true; | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { | |
| 58 | + // 打印 response 日志 | |
| 59 | + if (!SpringUtils.isProd()) { | |
| 60 | + StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH); | |
| 61 | + stopWatch.stop(); | |
| 62 | + log.info("[afterCompletion][完成请求 URL({}) 耗时({} ms)]", | |
| 63 | + request.getRequestURI(), stopWatch.getTotalTimeMillis()); | |
| 64 | + } | |
| 65 | + } | |
| 66 | + | |
| 67 | +} | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLog.java deleted
| 1 | -package cn.iocoder.yudao.framework.apilog.core.service; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | - | |
| 5 | -import jakarta.validation.constraints.NotNull; | |
| 6 | -import java.time.LocalDateTime; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * API 访问日志 | |
| 10 | - * | |
| 11 | - * @author 芋道源码 | |
| 12 | - */ | |
| 13 | -@Data | |
| 14 | -public class ApiAccessLog { | |
| 15 | - | |
| 16 | - /** | |
| 17 | - * 链路追踪编号 | |
| 18 | - */ | |
| 19 | - private String traceId; | |
| 20 | - /** | |
| 21 | - * 用户编号 | |
| 22 | - */ | |
| 23 | - private Long userId; | |
| 24 | - /** | |
| 25 | - * 用户类型 | |
| 26 | - */ | |
| 27 | - private Integer userType; | |
| 28 | - /** | |
| 29 | - * 应用名 | |
| 30 | - */ | |
| 31 | - @NotNull(message = "应用名不能为空") | |
| 32 | - private String applicationName; | |
| 33 | - | |
| 34 | - /** | |
| 35 | - * 请求方法名 | |
| 36 | - */ | |
| 37 | - @NotNull(message = "http 请求方法不能为空") | |
| 38 | - private String requestMethod; | |
| 39 | - /** | |
| 40 | - * 访问地址 | |
| 41 | - */ | |
| 42 | - @NotNull(message = "访问地址不能为空") | |
| 43 | - private String requestUrl; | |
| 44 | - /** | |
| 45 | - * 请求参数 | |
| 46 | - */ | |
| 47 | - @NotNull(message = "请求参数不能为空") | |
| 48 | - private String requestParams; | |
| 49 | - /** | |
| 50 | - * 用户 IP | |
| 51 | - */ | |
| 52 | - @NotNull(message = "ip 不能为空") | |
| 53 | - private String userIp; | |
| 54 | - /** | |
| 55 | - * 浏览器 UA | |
| 56 | - */ | |
| 57 | - @NotNull(message = "User-Agent 不能为空") | |
| 58 | - private String userAgent; | |
| 59 | - | |
| 60 | - /** | |
| 61 | - * 开始请求时间 | |
| 62 | - */ | |
| 63 | - @NotNull(message = "开始请求时间不能为空") | |
| 64 | - private LocalDateTime beginTime; | |
| 65 | - /** | |
| 66 | - * 结束请求时间 | |
| 67 | - */ | |
| 68 | - @NotNull(message = "结束请求时间不能为空") | |
| 69 | - private LocalDateTime endTime; | |
| 70 | - /** | |
| 71 | - * 执行时长,单位:毫秒 | |
| 72 | - */ | |
| 73 | - @NotNull(message = "执行时长不能为空") | |
| 74 | - private Integer duration; | |
| 75 | - /** | |
| 76 | - * 结果码 | |
| 77 | - */ | |
| 78 | - @NotNull(message = "错误码不能为空") | |
| 79 | - private Integer resultCode; | |
| 80 | - /** | |
| 81 | - * 结果提示 | |
| 82 | - */ | |
| 83 | - private String resultMsg; | |
| 84 | - | |
| 85 | -} |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkService.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.core.service; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; | |
| 4 | + | |
| 3 | 5 | /** |
| 4 | 6 | * API 访问日志 Framework Service 接口 |
| 5 | 7 | * |
| ... | ... | @@ -10,7 +12,8 @@ public interface ApiAccessLogFrameworkService { |
| 10 | 12 | /** |
| 11 | 13 | * 创建 API 访问日志 |
| 12 | 14 | * |
| 13 | - * @param apiAccessLog API 访问日志 | |
| 15 | + * @param reqDTO API 访问日志 | |
| 14 | 16 | */ |
| 15 | - void createApiAccessLog(ApiAccessLog apiAccessLog); | |
| 17 | + void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO); | |
| 18 | + | |
| 16 | 19 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.core.service; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.bean.BeanUtil; | |
| 4 | 3 | import cn.iocoder.yudao.module.infra.api.logger.ApiAccessLogApi; |
| 5 | 4 | import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO; |
| 6 | 5 | import lombok.RequiredArgsConstructor; |
| ... | ... | @@ -20,8 +19,7 @@ public class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkSe |
| 20 | 19 | |
| 21 | 20 | @Override |
| 22 | 21 | @Async |
| 23 | - public void createApiAccessLog(ApiAccessLog apiAccessLog) { | |
| 24 | - ApiAccessLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiAccessLog, ApiAccessLogCreateReqDTO.class); | |
| 22 | + public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) { | |
| 25 | 23 | apiAccessLogApi.createApiAccessLog(reqDTO); |
| 26 | 24 | } |
| 27 | 25 | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLog.java deleted
| 1 | -package cn.iocoder.yudao.framework.apilog.core.service; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | - | |
| 5 | -import jakarta.validation.constraints.NotNull; | |
| 6 | -import java.time.LocalDateTime; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * API 错误日志 | |
| 10 | - * | |
| 11 | - * @author 芋道源码 | |
| 12 | - */ | |
| 13 | -@Data | |
| 14 | -public class ApiErrorLog { | |
| 15 | - | |
| 16 | - /** | |
| 17 | - * 链路编号 | |
| 18 | - */ | |
| 19 | - private String traceId; | |
| 20 | - /** | |
| 21 | - * 账号编号 | |
| 22 | - */ | |
| 23 | - private Long userId; | |
| 24 | - /** | |
| 25 | - * 用户类型 | |
| 26 | - */ | |
| 27 | - private Integer userType; | |
| 28 | - /** | |
| 29 | - * 应用名 | |
| 30 | - */ | |
| 31 | - @NotNull(message = "应用名不能为空") | |
| 32 | - private String applicationName; | |
| 33 | - | |
| 34 | - /** | |
| 35 | - * 请求方法名 | |
| 36 | - */ | |
| 37 | - @NotNull(message = "http 请求方法不能为空") | |
| 38 | - private String requestMethod; | |
| 39 | - /** | |
| 40 | - * 访问地址 | |
| 41 | - */ | |
| 42 | - @NotNull(message = "访问地址不能为空") | |
| 43 | - private String requestUrl; | |
| 44 | - /** | |
| 45 | - * 请求参数 | |
| 46 | - */ | |
| 47 | - @NotNull(message = "请求参数不能为空") | |
| 48 | - private String requestParams; | |
| 49 | - /** | |
| 50 | - * 用户 IP | |
| 51 | - */ | |
| 52 | - @NotNull(message = "ip 不能为空") | |
| 53 | - private String userIp; | |
| 54 | - /** | |
| 55 | - * 浏览器 UA | |
| 56 | - */ | |
| 57 | - @NotNull(message = "User-Agent 不能为空") | |
| 58 | - private String userAgent; | |
| 59 | - | |
| 60 | - /** | |
| 61 | - * 异常时间 | |
| 62 | - */ | |
| 63 | - @NotNull(message = "异常时间不能为空") | |
| 64 | - private LocalDateTime exceptionTime; | |
| 65 | - /** | |
| 66 | - * 异常名 | |
| 67 | - */ | |
| 68 | - @NotNull(message = "异常名不能为空") | |
| 69 | - private String exceptionName; | |
| 70 | - /** | |
| 71 | - * 异常发生的类全名 | |
| 72 | - */ | |
| 73 | - @NotNull(message = "异常发生的类全名不能为空") | |
| 74 | - private String exceptionClassName; | |
| 75 | - /** | |
| 76 | - * 异常发生的类文件 | |
| 77 | - */ | |
| 78 | - @NotNull(message = "异常发生的类文件不能为空") | |
| 79 | - private String exceptionFileName; | |
| 80 | - /** | |
| 81 | - * 异常发生的方法名 | |
| 82 | - */ | |
| 83 | - @NotNull(message = "异常发生的方法名不能为空") | |
| 84 | - private String exceptionMethodName; | |
| 85 | - /** | |
| 86 | - * 异常发生的方法所在行 | |
| 87 | - */ | |
| 88 | - @NotNull(message = "异常发生的方法所在行不能为空") | |
| 89 | - private Integer exceptionLineNumber; | |
| 90 | - /** | |
| 91 | - * 异常的栈轨迹异常的栈轨迹 | |
| 92 | - */ | |
| 93 | - @NotNull(message = "异常的栈轨迹不能为空") | |
| 94 | - private String exceptionStackTrace; | |
| 95 | - /** | |
| 96 | - * 异常导致的根消息 | |
| 97 | - */ | |
| 98 | - @NotNull(message = "异常导致的根消息不能为空") | |
| 99 | - private String exceptionRootCauseMessage; | |
| 100 | - /** | |
| 101 | - * 异常导致的消息 | |
| 102 | - */ | |
| 103 | - @NotNull(message = "异常导致的消息不能为空") | |
| 104 | - private String exceptionMessage; | |
| 105 | - | |
| 106 | - | |
| 107 | -} |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkService.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.core.service; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; | |
| 4 | + | |
| 3 | 5 | /** |
| 4 | 6 | * API 错误日志 Framework Service 接口 |
| 5 | 7 | * |
| ... | ... | @@ -10,7 +12,8 @@ public interface ApiErrorLogFrameworkService { |
| 10 | 12 | /** |
| 11 | 13 | * 创建 API 错误日志 |
| 12 | 14 | * |
| 13 | - * @param apiErrorLog API 错误日志 | |
| 15 | + * @param reqDTO API 错误日志 | |
| 14 | 16 | */ |
| 15 | - void createApiErrorLog(ApiErrorLog apiErrorLog); | |
| 17 | + void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO); | |
| 18 | + | |
| 16 | 19 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java
| 1 | 1 | package cn.iocoder.yudao.framework.apilog.core.service; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.bean.BeanUtil; | |
| 4 | 3 | import cn.iocoder.yudao.module.infra.api.logger.ApiErrorLogApi; |
| 5 | 4 | import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; |
| 6 | 5 | import lombok.RequiredArgsConstructor; |
| ... | ... | @@ -20,8 +19,7 @@ public class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkServ |
| 20 | 19 | |
| 21 | 20 | @Override |
| 22 | 21 | @Async |
| 23 | - public void createApiErrorLog(ApiErrorLog apiErrorLog) { | |
| 24 | - ApiErrorLogCreateReqDTO reqDTO = BeanUtil.copyProperties(apiErrorLog, ApiErrorLogCreateReqDTO.class); | |
| 22 | + public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) { | |
| 25 | 23 | apiErrorLogApi.createApiErrorLog(reqDTO); |
| 26 | 24 | } |
| 27 | 25 | ... | ... |
yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/handler/GlobalExceptionHandler.java
| ... | ... | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.framework.web.core.handler; |
| 3 | 3 | import cn.hutool.core.exceptions.ExceptionUtil; |
| 4 | 4 | import cn.hutool.core.map.MapUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | -import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog; | |
| 7 | 6 | import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLogFrameworkService; |
| 8 | 7 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 9 | 8 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| ... | ... | @@ -11,6 +10,7 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 11 | 10 | import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; |
| 12 | 11 | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| 13 | 12 | import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; |
| 13 | +import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO; | |
| 14 | 14 | import lombok.AllArgsConstructor; |
| 15 | 15 | import lombok.extern.slf4j.Slf4j; |
| 16 | 16 | import org.apache.commons.lang3.exception.ExceptionUtils; |
| ... | ... | @@ -46,6 +46,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC |
| 46 | 46 | @Slf4j |
| 47 | 47 | public class GlobalExceptionHandler { |
| 48 | 48 | |
| 49 | + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") | |
| 49 | 50 | private final String applicationName; |
| 50 | 51 | |
| 51 | 52 | private final ApiErrorLogFrameworkService apiErrorLogFrameworkService; |
| ... | ... | @@ -237,10 +238,10 @@ public class GlobalExceptionHandler { |
| 237 | 238 | |
| 238 | 239 | private void createExceptionLog(HttpServletRequest req, Throwable e) { |
| 239 | 240 | // 插入错误日志 |
| 240 | - ApiErrorLog errorLog = new ApiErrorLog(); | |
| 241 | + ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO(); | |
| 241 | 242 | try { |
| 242 | 243 | // 初始化 errorLog |
| 243 | - initExceptionLog(errorLog, req, e); | |
| 244 | + buildExceptionLog(errorLog, req, e); | |
| 244 | 245 | // 执行插入 errorLog |
| 245 | 246 | apiErrorLogFrameworkService.createApiErrorLog(errorLog); |
| 246 | 247 | } catch (Throwable th) { |
| ... | ... | @@ -248,7 +249,7 @@ public class GlobalExceptionHandler { |
| 248 | 249 | } |
| 249 | 250 | } |
| 250 | 251 | |
| 251 | - private void initExceptionLog(ApiErrorLog errorLog, HttpServletRequest request, Throwable e) { | |
| 252 | + private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) { | |
| 252 | 253 | // 处理用户信息 |
| 253 | 254 | errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request)); |
| 254 | 255 | errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); | ... | ... |
yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/api/logger/dto/ApiAccessLogCreateReqDTO.java
| ... | ... | @@ -44,9 +44,12 @@ public class ApiAccessLogCreateReqDTO { |
| 44 | 44 | /** |
| 45 | 45 | * 请求参数 |
| 46 | 46 | */ |
| 47 | - @NotNull(message = "请求参数不能为空") | |
| 48 | 47 | private String requestParams; |
| 49 | 48 | /** |
| 49 | + * 响应结果 | |
| 50 | + */ | |
| 51 | + private String responseBody; | |
| 52 | + /** | |
| 50 | 53 | * 用户 IP |
| 51 | 54 | */ |
| 52 | 55 | @NotNull(message = "ip 不能为空") |
| ... | ... | @@ -58,6 +61,21 @@ public class ApiAccessLogCreateReqDTO { |
| 58 | 61 | private String userAgent; |
| 59 | 62 | |
| 60 | 63 | /** |
| 64 | + * 操作模块 | |
| 65 | + */ | |
| 66 | + private String operateModule; | |
| 67 | + /** | |
| 68 | + * 操作名 | |
| 69 | + */ | |
| 70 | + private String operateName; | |
| 71 | + /** | |
| 72 | + * 操作分类 | |
| 73 | + * | |
| 74 | + * 枚举,参见 OperateTypeEnum 类 | |
| 75 | + */ | |
| 76 | + private Integer operateType; | |
| 77 | + | |
| 78 | + /** | |
| 61 | 79 | * 开始请求时间 |
| 62 | 80 | */ |
| 63 | 81 | @NotNull(message = "开始请求时间不能为空") | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java
| ... | ... | @@ -48,6 +48,10 @@ public class ApiAccessLogRespVO { |
| 48 | 48 | @ExcelProperty("请求参数") |
| 49 | 49 | private String requestParams; |
| 50 | 50 | |
| 51 | + @Schema(description = "响应结果") | |
| 52 | + @ExcelProperty("响应结果") | |
| 53 | + private String responseBody; | |
| 54 | + | |
| 51 | 55 | @Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1") |
| 52 | 56 | @ExcelProperty("用户 IP") |
| 53 | 57 | private String userIp; |
| ... | ... | @@ -56,6 +60,19 @@ public class ApiAccessLogRespVO { |
| 56 | 60 | @ExcelProperty("浏览器 UA") |
| 57 | 61 | private String userAgent; |
| 58 | 62 | |
| 63 | + @Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品模块") | |
| 64 | + @ExcelProperty("操作模块") | |
| 65 | + private String operateModule; | |
| 66 | + | |
| 67 | + @Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建商品") | |
| 68 | + @ExcelProperty("操作名") | |
| 69 | + private String operateName; | |
| 70 | + | |
| 71 | + @Schema(description = "操作分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 72 | + @ExcelProperty(value = "操作分类", converter = DictConvert.class) | |
| 73 | + @DictFormat(DictTypeConstants.OPERATE_TYPE) | |
| 74 | + private Integer operateType; | |
| 75 | + | |
| 59 | 76 | @Schema(description = "开始请求时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 60 | 77 | @ExcelProperty("开始请求时间") |
| 61 | 78 | private LocalDateTime beginTime; | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/dal/dataobject/logger/ApiAccessLogDO.java
| 1 | 1 | package cn.iocoder.yudao.module.infra.dal.dataobject.logger; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 6 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| ... | ... | @@ -71,6 +72,10 @@ public class ApiAccessLogDO extends BaseDO { |
| 71 | 72 | */ |
| 72 | 73 | private String requestParams; |
| 73 | 74 | /** |
| 75 | + * 响应结果 | |
| 76 | + */ | |
| 77 | + private String responseBody; | |
| 78 | + /** | |
| 74 | 79 | * 用户 IP |
| 75 | 80 | */ |
| 76 | 81 | private String userIp; |
| ... | ... | @@ -82,6 +87,21 @@ public class ApiAccessLogDO extends BaseDO { |
| 82 | 87 | // ========== 执行相关字段 ========== |
| 83 | 88 | |
| 84 | 89 | /** |
| 90 | + * 操作模块 | |
| 91 | + */ | |
| 92 | + private String operateModule; | |
| 93 | + /** | |
| 94 | + * 操作名 | |
| 95 | + */ | |
| 96 | + private String operateName; | |
| 97 | + /** | |
| 98 | + * 操作分类 | |
| 99 | + * | |
| 100 | + * 枚举 {@link OperateTypeEnum} | |
| 101 | + */ | |
| 102 | + private Integer operateType; | |
| 103 | + | |
| 104 | + /** | |
| 85 | 105 | * 开始请求时间 |
| 86 | 106 | */ |
| 87 | 107 | private LocalDateTime beginTime; |
| ... | ... | @@ -93,6 +113,7 @@ public class ApiAccessLogDO extends BaseDO { |
| 93 | 113 | * 执行时长,单位:毫秒 |
| 94 | 114 | */ |
| 95 | 115 | private Integer duration; |
| 116 | + | |
| 96 | 117 | /** |
| 97 | 118 | * 结果码 |
| 98 | 119 | * | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/test/resources/sql/create_tables.sql
| ... | ... | @@ -94,8 +94,12 @@ CREATE TABLE IF NOT EXISTS "infra_api_access_log" ( |
| 94 | 94 | "request_method" varchar(16) not null default '', |
| 95 | 95 | "request_url" varchar(255) not null default '', |
| 96 | 96 | "request_params" varchar(8000) not null default '', |
| 97 | + "response_body" varchar(8000) not null default '', | |
| 97 | 98 | "user_ip" varchar(50) not null, |
| 98 | 99 | "user_agent" varchar(512) not null, |
| 100 | + `operate_module` varchar(50) NOT NULL, | |
| 101 | + `operate_name` varchar(50) NOT NULL, | |
| 102 | + `operate_type` bigint(4) NOT NULL DEFAULT '0', | |
| 99 | 103 | "begin_time" timestamp not null, |
| 100 | 104 | "end_time" timestamp not null, |
| 101 | 105 | "duration" integer not null, |
| ... | ... | @@ -108,7 +112,7 @@ CREATE TABLE IF NOT EXISTS "infra_api_access_log" ( |
| 108 | 112 | "deleted" bit not null default false, |
| 109 | 113 | "tenant_id" bigint not null default '0', |
| 110 | 114 | primary key ("id") |
| 111 | - ) COMMENT 'API 访问日志表'; | |
| 115 | +) COMMENT 'API 访问日志表'; | |
| 112 | 116 | |
| 113 | 117 | CREATE TABLE IF NOT EXISTS "infra_api_error_log" ( |
| 114 | 118 | "id" bigint not null GENERATED BY DEFAULT AS IDENTITY, | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/notify/NotifyMessageController.java
| 1 | 1 | package cn.iocoder.yudao.module.system.controller.admin.notify; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.apilog.core.annotations.ApiAccessLog; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| ... | ... | @@ -88,6 +89,7 @@ public class NotifyMessageController { |
| 88 | 89 | |
| 89 | 90 | @GetMapping("/get-unread-count") |
| 90 | 91 | @Operation(summary = "获得当前用户的未读站内信数量") |
| 92 | + @ApiAccessLog(enable = false) // 由于前端会不断轮询该接口,记录日志没有意义 | |
| 91 | 93 | public CommonResult<Long> getUnreadNotifyMessageCount() { |
| 92 | 94 | return success(notifyMessageService.getUnreadNotifyMessageCount( |
| 93 | 95 | getLoginUserId(), UserTypeEnum.ADMIN.getValue())); | ... | ... |