From 4559f7268fb3f92592fe17fd8786f8d3ee5ab7ad Mon Sep 17 00:00:00 2001 From: chenbowen Date: Thu, 28 Aug 2025 15:47:49 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E8=B0=83=E6=95=B4=20e=20=E5=8A=9E?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E7=94=A8=E6=88=B7=E7=BB=84=E7=BB=87=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=8A=A5=E6=96=87=E6=8E=A5=E5=8F=97=E9=80=BB=E8=BE=91?= =?UTF-8?q?=202.=20=E6=96=B0=E5=A2=9E=E9=92=88=E5=AF=B9=20e=20=E5=8A=9E?= =?UTF-8?q?=E7=9A=84=E6=8E=A5=E5=8F=A3=E6=97=A5=E5=BF=97=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 28aae25befabba479e977142f73e4a41b32d3988) --- sql/dm/system_sync_log.sql | 83 ++++ sql/mysql/sync_log_dict.sql | 11 + sql/mysql/sync_log_menu.sql | 17 + sql/mysql/system_sync_log.sql | 41 ++ .../system/enums/dept/DeptTypeEnum.java | 63 +++ .../admin/dept/vo/dept/DeptSaveReqVO.java | 4 +- .../controller/admin/sync/SyncController.java | 452 +++++++++++++++--- .../admin/sync/SyncLogController.java | 52 ++ .../admin/sync/vo/SyncLogPageReqVO.java | 44 ++ .../admin/sync/vo/SyncLogRespVO.java | 92 ++++ .../admin/sync/vo/org/OrgCreateRequestVO.java | 6 +- .../admin/sync/vo/org/OrgUpdateRequestVO.java | 4 +- .../admin/user/vo/user/UserSaveReqVO.java | 2 +- .../system/dal/dataobject/sync/SyncLogDO.java | 166 +++++++ .../system/dal/mysql/sync/SyncLogMapper.java | 29 ++ .../system/enums/sync/SyncLogStatusEnum.java | 31 ++ .../service/sync/OrgSyncServiceImpl.java | 19 +- .../system/service/sync/SyncLogService.java | 102 ++++ .../service/sync/SyncLogServiceImpl.java | 148 ++++++ .../service/sync/UserSyncServiceImpl.java | 21 +- .../service/user/AdminUserServiceImpl.java | 7 + .../system/util/sync/SyncVerifyUtil.java | 45 +- .../src/main/resources/application.yaml | 1 + .../system/util/sync/SyncVerifyUtilTest.java | 46 ++ .../src/main/resources/application-local.yaml | 46 +- .../src/main/resources/application.yaml | 7 + 26 files changed, 1420 insertions(+), 119 deletions(-) create mode 100644 sql/dm/system_sync_log.sql create mode 100644 sql/mysql/sync_log_dict.sql create mode 100644 sql/mysql/sync_log_menu.sql create mode 100644 sql/mysql/system_sync_log.sql create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dept/DeptTypeEnum.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncLogController.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogPageReqVO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogRespVO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sync/SyncLogDO.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sync/SyncLogMapper.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/enums/sync/SyncLogStatusEnum.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogService.java create mode 100644 yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogServiceImpl.java create mode 100644 yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtilTest.java diff --git a/sql/dm/system_sync_log.sql b/sql/dm/system_sync_log.sql new file mode 100644 index 00000000..985ac26c --- /dev/null +++ b/sql/dm/system_sync_log.sql @@ -0,0 +1,83 @@ +/* + Yudao Database Transfer Tool + + Source Server Type : MySQL + + Target Server Type : DM8 + + Date: 2025-08-28 14:12:23 +*/ + + +-- ---------------------------- +-- Table structure for system_sync_log +-- ---------------------------- +CREATE TABLE system_sync_log ( + id bigint NOT NULL PRIMARY KEY, + bim_request_id varchar(64) DEFAULT '' NULL, + service_name varchar(100) NOT NULL, + request_method varchar(16) DEFAULT 'POST' NOT NULL, + request_url varchar(500) NOT NULL, + client_ip varchar(50) NOT NULL, + user_agent varchar(512) DEFAULT NULL NULL, + request_time datetime NOT NULL, + response_time datetime DEFAULT NULL NULL, + duration bigint DEFAULT NULL NULL, + encrypted_request text NOT NULL, + decrypted_request text NULL, + status smallint DEFAULT 0 NOT NULL, + error_code varchar(100) DEFAULT NULL NULL, + error_message varchar(1000) DEFAULT NULL NULL, + exception_stack text NULL, + response_data text NULL, + encrypted_response text NULL, + auth_user varchar(100) DEFAULT NULL NULL, + decrypt_status smallint DEFAULT 0 NOT NULL, + signature_verify_status smallint DEFAULT 0 NOT NULL, + auth_status smallint DEFAULT 0 NOT NULL, + business_result varchar(100) DEFAULT NULL NULL, + extra text NULL, + creator varchar(64) DEFAULT '' NULL, + create_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + updater varchar(64) DEFAULT '' NULL, + update_time datetime DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted bit DEFAULT '0' NOT NULL +); + +CREATE INDEX idx_system_sync_log_01 ON system_sync_log (bim_request_id); +CREATE INDEX idx_system_sync_log_02 ON system_sync_log (service_name); +CREATE INDEX idx_system_sync_log_03 ON system_sync_log (request_time); +CREATE INDEX idx_system_sync_log_04 ON system_sync_log (status); +CREATE INDEX idx_system_sync_log_05 ON system_sync_log (client_ip); +CREATE INDEX idx_system_sync_log_06 ON system_sync_log (auth_user); + +COMMENT ON COLUMN system_sync_log.id IS '日志主键'; +COMMENT ON COLUMN system_sync_log.bim_request_id IS '外部请求ID'; +COMMENT ON COLUMN system_sync_log.service_name IS '接口名称/服务名称'; +COMMENT ON COLUMN system_sync_log.request_method IS '请求方法'; +COMMENT ON COLUMN system_sync_log.request_url IS '请求URL'; +COMMENT ON COLUMN system_sync_log.client_ip IS '客户端IP地址'; +COMMENT ON COLUMN system_sync_log.user_agent IS '用户代理字符串'; +COMMENT ON COLUMN system_sync_log.request_time IS '请求开始时间'; +COMMENT ON COLUMN system_sync_log.response_time IS '请求结束时间'; +COMMENT ON COLUMN system_sync_log.duration IS '请求处理耗时(毫秒)'; +COMMENT ON COLUMN system_sync_log.encrypted_request IS '原始加密请求体'; +COMMENT ON COLUMN system_sync_log.decrypted_request IS '解密后的请求体'; +COMMENT ON COLUMN system_sync_log.status IS '响应状态 0-成功 1-解密失败 2-签名验证失败 3-认证失败 4-业务处理失败 5-系统异常'; +COMMENT ON COLUMN system_sync_log.error_code IS '错误码'; +COMMENT ON COLUMN system_sync_log.error_message IS '错误信息'; +COMMENT ON COLUMN system_sync_log.exception_stack IS '异常堆栈'; +COMMENT ON COLUMN system_sync_log.response_data IS '响应数据(加密前)'; +COMMENT ON COLUMN system_sync_log.encrypted_response IS '加密后的响应数据'; +COMMENT ON COLUMN system_sync_log.auth_user IS '认证用户'; +COMMENT ON COLUMN system_sync_log.decrypt_status IS '解密状态 0-成功 1-失败'; +COMMENT ON COLUMN system_sync_log.signature_verify_status IS '签名验证状态 0-成功 1-失败'; +COMMENT ON COLUMN system_sync_log.auth_status IS '认证状态 0-成功 1-失败'; +COMMENT ON COLUMN system_sync_log.business_result IS '业务处理结果'; +COMMENT ON COLUMN system_sync_log.extra IS '额外信息'; +COMMENT ON COLUMN system_sync_log.creator IS '创建者'; +COMMENT ON COLUMN system_sync_log.create_time IS '创建时间'; +COMMENT ON COLUMN system_sync_log.updater IS '更新者'; +COMMENT ON COLUMN system_sync_log.update_time IS '更新时间'; +COMMENT ON COLUMN system_sync_log.deleted IS '是否删除'; +COMMENT ON TABLE system_sync_log IS '同步接口调用日志表'; diff --git a/sql/mysql/sync_log_dict.sql b/sql/mysql/sync_log_dict.sql new file mode 100644 index 00000000..39f39526 --- /dev/null +++ b/sql/mysql/sync_log_dict.sql @@ -0,0 +1,11 @@ +-- 同步日志状态字典数据 +INSERT INTO system_dict_type (id, name, type, status, remark, creator, create_time, updater, update_time, deleted) VALUES +(999, '同步日志状态', 'sync_log_status', 0, '同步接口调用日志状态', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'); + +INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES +(9990, 1, '成功', '0', 'sync_log_status', 0, 'success', '', '同步接口调用成功', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'), +(9991, 2, '解密失败', '1', 'sync_log_status', 0, 'danger', '', '请求解密失败', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'), +(9992, 3, '签名验证失败', '2', 'sync_log_status', 0, 'warning', '', '签名验证失败', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'), +(9993, 4, '认证失败', '3', 'sync_log_status', 0, 'warning', '', '用户认证失败', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'), +(9994, 5, '业务处理失败', '4', 'sync_log_status', 0, 'danger', '', '业务逻辑处理失败', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'), +(9995, 6, '系统异常', '5', 'sync_log_status', 0, 'danger', '', '系统内部异常', 'admin', '2024-01-01 00:00:00', 'admin', '2024-01-01 00:00:00', b'0'); diff --git a/sql/mysql/sync_log_menu.sql b/sql/mysql/sync_log_menu.sql new file mode 100644 index 00000000..241f7844 --- /dev/null +++ b/sql/mysql/sync_log_menu.sql @@ -0,0 +1,17 @@ +-- 同步接口日志菜单权限 SQL 脚本 +-- 为同步接口日志查询界面创建相应的菜单和权限 + +-- 在审计日志(108)下新增同步日志菜单 +INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES (502, '同步日志', '', 2, 3, 108, 'sync-log', 'ep:data-line', 'system/synclog/index', 'SystemSyncLog', 0, '1', '1', '1', 'admin', '2024-08-28 00:00:00', 'admin', '2024-08-28 00:00:00', '0'); + +-- 同步日志查询权限按钮 +INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) +VALUES (5020, '同步日志查询', 'system:sync-log:query', 3, 1, 502, '', '', '', '', 0, '1', '1', '1', 'admin', '2024-08-28 00:00:00', 'admin', '2024-08-28 00:00:00', '0'); + +-- 为超级管理员角色分配菜单权限 +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted) +VALUES (NULL, 1, 502, 'admin', '2024-08-28 00:00:00', 'admin', '2024-08-28 00:00:00', '0'); + +INSERT INTO system_role_menu (id, role_id, menu_id, creator, create_time, updater, update_time, deleted) +VALUES (NULL, 1, 5020, 'admin', '2024-08-28 00:00:00', 'admin', '2024-08-28 00:00:00', '0'); diff --git a/sql/mysql/system_sync_log.sql b/sql/mysql/system_sync_log.sql new file mode 100644 index 00000000..b733cfda --- /dev/null +++ b/sql/mysql/system_sync_log.sql @@ -0,0 +1,41 @@ +-- Table structure for system_sync_log +-- ---------------------------- +DROP TABLE IF EXISTS `system_sync_log`; +CREATE TABLE `system_sync_log` ( + `id` bigint NOT NULL COMMENT '日志主键', + `bim_request_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '外部请求ID', + `service_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接口名称/服务名称', + `request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'POST' COMMENT '请求方法', + `request_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '请求URL', + `client_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '客户端IP地址', + `user_agent` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户代理字符串', + `request_time` datetime NOT NULL COMMENT '请求开始时间', + `response_time` datetime NULL DEFAULT NULL COMMENT '请求结束时间', + `duration` bigint NULL DEFAULT NULL COMMENT '请求处理耗时(毫秒)', + `encrypted_request` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '原始加密请求体', + `decrypted_request` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '解密后的请求体', + `status` tinyint NOT NULL DEFAULT 0 COMMENT '响应状态 0-成功 1-解密失败 2-签名验证失败 3-认证失败 4-业务处理失败 5-系统异常', + `error_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '错误码', + `error_message` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '错误信息', + `exception_stack` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '异常堆栈', + `response_data` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '响应数据(加密前)', + `encrypted_response` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '加密后的响应数据', + `auth_user` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '认证用户', + `decrypt_status` tinyint NOT NULL DEFAULT 0 COMMENT '解密状态 0-成功 1-失败', + `signature_verify_status` tinyint NOT NULL DEFAULT 0 COMMENT '签名验证状态 0-成功 1-失败', + `auth_status` tinyint NOT NULL DEFAULT 0 COMMENT '认证状态 0-成功 1-失败', + `business_result` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '业务处理结果', + `extra` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '额外信息', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE, + INDEX `idx_bim_request_id` (`bim_request_id`) USING BTREE COMMENT '外部请求ID索引', + INDEX `idx_service_name` (`service_name`) USING BTREE COMMENT '服务名称索引', + INDEX `idx_request_time` (`request_time`) USING BTREE COMMENT '请求时间索引', + INDEX `idx_status` (`status`) USING BTREE COMMENT '状态索引', + INDEX `idx_client_ip` (`client_ip`) USING BTREE COMMENT '客户端IP索引', + INDEX `idx_auth_user` (`auth_user`) USING BTREE COMMENT '认证用户索引' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '同步接口调用日志表'; diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dept/DeptTypeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dept/DeptTypeEnum.java new file mode 100644 index 00000000..9e0ee946 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/dept/DeptTypeEnum.java @@ -0,0 +1,63 @@ +package cn.iocoder.yudao.module.system.enums.dept; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 机构类型枚举 + * + * @author chenbowen + */ +@Getter +@AllArgsConstructor +public enum DeptTypeEnum { + + /** + * 单位/公司 + */ + COMPANY(28, "单位"), + + /** + * 部门 + */ + DEPARTMENT(26, "部门"); + + /** + * 类型编码 + */ + private final Integer code; + + /** + * 类型名称 + */ + private final String name; + + /** + * 根据编码获取枚举 + */ + public static DeptTypeEnum getByCode(Integer code) { + if (code == null) { + return null; + } + for (DeptTypeEnum value : DeptTypeEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + + /** + * 判断是否为公司 + */ + public static boolean isCompany(Integer code) { + return COMPANY.getCode().equals(code); + } + + /** + * 判断是否为部门 + */ + public static boolean isDepartment(Integer code) { + return DEPARTMENT.getCode().equals(code); + } +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java index 79ae089c..78b6be18 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java @@ -24,7 +24,7 @@ public class DeptSaveReqVO { @Size(max = 30, message = "部门名称长度不能超过 30 个字符") private String name; - @Schema(description = "父部门 ID", example = "1024") + @Schema(description = "父部门 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024(顶级部门父级 Id 为 0)") private Long parentId; @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @@ -43,7 +43,7 @@ public class DeptSaveReqVO { @Size(max = 50, message = "邮箱长度不能超过 50 个字符") private String email; - @Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @Schema(description = "状态,见 CommonStatusEnum 枚举0 开启 1 关闭", requiredMode = Schema.RequiredMode.REQUIRED, example = "0 开启 1 关闭") @InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}") private Integer status; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncController.java index 99c8fd04..389b242f 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncController.java @@ -11,9 +11,11 @@ import cn.iocoder.yudao.module.system.controller.admin.sync.vo.org.*; import cn.iocoder.yudao.module.system.controller.admin.sync.vo.schema.SchemaRequestVO; import cn.iocoder.yudao.module.system.controller.admin.sync.vo.schema.SchemaResponseVO; import cn.iocoder.yudao.module.system.controller.admin.sync.vo.user.*; +import cn.iocoder.yudao.module.system.enums.sync.SyncLogStatusEnum; import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; import cn.iocoder.yudao.module.system.service.sync.OrgSyncService; import cn.iocoder.yudao.module.system.service.sync.SchemaSyncService; +import cn.iocoder.yudao.module.system.service.sync.SyncLogService; import cn.iocoder.yudao.module.system.service.sync.UserSyncService; import cn.iocoder.yudao.module.system.util.sync.SyncVerifyUtil; import com.alibaba.fastjson.JSON; @@ -25,6 +27,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -51,23 +55,50 @@ public class SyncController { private AdminAuthService adminAuthService; @Resource private OAuth2TokenCommonApi oauth2TokenApi; + @Resource + private SyncLogService syncLogService; @PostMapping("/SchemaService") @TenantIgnore @PermitAll public String getSchema(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("SchemaService", + "/system/sync/SchemaService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - SchemaRequestVO req = decryptRequest(encryptedBody, SchemaRequestVO.class); + SchemaRequestVO req = decryptRequest(encryptedBody, SchemaRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(schemaService.getSchema(req)); + + // 业务处理 + SchemaResponseVO response = schemaService.getSchema(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { SchemaResponseVO errorResp = new SchemaResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -76,17 +107,42 @@ public class SyncController { @TenantIgnore @PermitAll public String createOrg(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("OrgCreateService", + "/system/sync/OrgCreateService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - OrgCreateRequestVO req = decryptRequest(encryptedBody, OrgCreateRequestVO.class); + OrgCreateRequestVO req = decryptRequest(encryptedBody, OrgCreateRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(organizationService.createOrg(req)); + + // 业务处理 + OrgCreateResponseVO response = organizationService.createOrg(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { OrgCreateResponseVO errorResp = new OrgCreateResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -95,17 +151,42 @@ public class SyncController { @TenantIgnore @PermitAll public String deleteOrg(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("OrgDeleteService", + "/system/sync/OrgDeleteService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - OrgDeleteRequestVO req = decryptRequest(encryptedBody, OrgDeleteRequestVO.class); + OrgDeleteRequestVO req = decryptRequest(encryptedBody, OrgDeleteRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(organizationService.deleteOrg(req)); + + // 业务处理 + OrgDeleteResponseVO response = organizationService.deleteOrg(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { OrgDeleteResponseVO errorResp = new OrgDeleteResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -114,17 +195,42 @@ public class SyncController { @TenantIgnore @PermitAll public String updateOrg(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("OrgUpdateService", + "/system/sync/OrgUpdateService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - OrgUpdateRequestVO req = decryptRequest(encryptedBody, OrgUpdateRequestVO.class); + OrgUpdateRequestVO req = decryptRequest(encryptedBody, OrgUpdateRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(organizationService.updateOrg(req)); + + // 业务处理 + OrgUpdateResponseVO response = organizationService.updateOrg(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { OrgUpdateResponseVO errorResp = new OrgUpdateResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -133,17 +239,42 @@ public class SyncController { @TenantIgnore @PermitAll public String listOrg(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("QueryAllOrgIdsService", + "/system/sync/QueryAllOrgIdsService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - OrgListRequestVO req = decryptRequest(encryptedBody, OrgListRequestVO.class); + OrgListRequestVO req = decryptRequest(encryptedBody, OrgListRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(organizationService.listOrgIds(req)); + + // 业务处理 + OrgListResponseVO response = organizationService.listOrgIds(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { OrgListResponseVO errorResp = new OrgListResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -152,17 +283,42 @@ public class SyncController { @TenantIgnore @PermitAll public String getOrg(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("QueryOrgByIdService", + "/system/sync/QueryOrgByIdService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - OrgGetRequestVO req = decryptRequest(encryptedBody, OrgGetRequestVO.class); + OrgGetRequestVO req = decryptRequest(encryptedBody, OrgGetRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(organizationService.getOrgById(req)); + + // 业务处理 + OrgGetResponseVO response = organizationService.getOrgById(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { OrgGetResponseVO errorResp = new OrgGetResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -171,17 +327,42 @@ public class SyncController { @TenantIgnore @PermitAll public String createUser(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("UserCreateService", + "/system/sync/UserCreateService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - UserCreateRequestVO req = decryptRequest(encryptedBody, UserCreateRequestVO.class); + UserCreateRequestVO req = decryptRequest(encryptedBody, UserCreateRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(accountService.createUser(req)); + + // 业务处理 + UserCreateResponseVO response = accountService.createUser(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { UserCreateResponseVO errorResp = new UserCreateResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -190,17 +371,42 @@ public class SyncController { @TenantIgnore @PermitAll public String deleteUser(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("UserDeleteService", + "/system/sync/UserDeleteService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - UserDeleteRequestVO req = decryptRequest(encryptedBody, UserDeleteRequestVO.class); + UserDeleteRequestVO req = decryptRequest(encryptedBody, UserDeleteRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(accountService.deleteUser(req)); + + // 业务处理 + UserDeleteResponseVO response = accountService.deleteUser(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { UserDeleteResponseVO errorResp = new UserDeleteResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -209,17 +415,42 @@ public class SyncController { @TenantIgnore @PermitAll public String updateUser(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("UserUpdateService", + "/system/sync/UserUpdateService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - UserUpdateRequestVO req = decryptRequest(encryptedBody, UserUpdateRequestVO.class); + UserUpdateRequestVO req = decryptRequest(encryptedBody, UserUpdateRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(accountService.updateUser(req)); + + // 业务处理 + UserUpdateResponseVO response = accountService.updateUser(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { UserUpdateResponseVO errorResp = new UserUpdateResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -228,17 +459,42 @@ public class SyncController { @TenantIgnore @PermitAll public String listUser(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("QueryAllUserIdsService", + "/system/sync/QueryAllUserIdsService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - UserListRequestVO req = decryptRequest(encryptedBody, UserListRequestVO.class); + UserListRequestVO req = decryptRequest(encryptedBody, UserListRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(accountService.listUserIds(req)); + + // 业务处理 + UserListResponseVO response = accountService.listUserIds(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { UserListResponseVO errorResp = new UserListResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } @@ -247,49 +503,105 @@ public class SyncController { @TenantIgnore @PermitAll public String getUser(@RequestBody String encryptedBody) { + // 记录请求开始 + Long logId = syncLogService.logRequestStart("QueryUserByIdService", + "/system/sync/QueryUserByIdService", + getClientIp(), + getRequest().getHeader("User-Agent"), + encryptedBody); + String bimRequestId = null; try { - UserGetRequestVO req = decryptRequest(encryptedBody, UserGetRequestVO.class); + UserGetRequestVO req = decryptRequest(encryptedBody, UserGetRequestVO.class, logId); bimRequestId = req.getBimRequestId(); - return encrypt(accountService.getUserById(req)); + + // 业务处理 + UserGetResponseVO response = accountService.getUserById(req); + String responseJson = JSON.toJSONString(response); + String encryptedResponse = encrypt(response); + + // 记录成功完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SUCCESS.getStatus(), + null, null, null, responseJson, encryptedResponse, response.getResultCode()); + + return encryptedResponse; } catch (Exception e) { UserGetResponseVO errorResp = new UserGetResponseVO(); errorResp.setBimRequestId(bimRequestId); errorResp.setResultCode("500"); errorResp.setMessage(e.getMessage()); - return encrypt(errorResp); + + String responseJson = JSON.toJSONString(errorResp); + String encryptedResponse = encrypt(errorResp); + + // 记录异常完成 + syncLogService.logRequestComplete(logId, SyncLogStatusEnum.SYSTEM_ERROR.getStatus(), + "SYSTEM_ERROR", e.getMessage(), getStackTrace(e), responseJson, encryptedResponse, "500"); + + return encryptedResponse; } } - private T decryptRequest(String encryptedBody, Class clazz) { - String bodyJson; - try { - bodyJson = SyncVerifyUtil.decrypt(encryptedBody, encryptKey, "AES"); - } catch (Exception e) { - throw exception(SYNC_DECRYPT_TYPE); - } - Map map = JSON.parseObject(bodyJson, Map.class); - if (!SyncVerifyUtil.verifySignature(map, "MD5")) { - throw exception(SYNC_SIGNATURE_VERIFY_FAILED); - } - AuthLoginReqVO authLoginReqVO = new AuthLoginReqVO(); - authLoginReqVO.setUsername((String) map.getOrDefault("bimRemoteUser", "")); - authLoginReqVO.setPassword((String) map.getOrDefault("bimRemotePwd", "")); - authLoginReqVO.setCaptchaVerification(encryptKey); - AuthLoginRespVO login = adminAuthService.login(authLoginReqVO); - // 校验访问令牌 + private T decryptRequest(String encryptedBody, Class clazz, Long logId) { + String bodyJson = ""; + String bimRequestId = null; + String authUser = null; - OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(login.getAccessToken()).getCheckedData(); - if (accessToken == null) { - throw exception(AUTH_LOGIN_USER_DISABLED); + try { + // 解密 + bodyJson = SyncVerifyUtil.decrypt(encryptedBody, encryptKey, "AES"); + Map map = JSON.parseObject(bodyJson, Map.class); + bimRequestId = (String) map.get("bimRequestId"); + authUser = (String) map.getOrDefault("bimRemoteUser", ""); + + // 记录解密成功 + syncLogService.logDecryptResult(logId, bimRequestId, bodyJson, authUser, true); + + // 签名验证 + boolean signatureValid = SyncVerifyUtil.verifySignature(map, "MD5"); + syncLogService.logSignatureVerifyResult(logId, signatureValid); + if (!signatureValid) { + throw exception(SYNC_SIGNATURE_VERIFY_FAILED); + } + + // 用户认证 + AuthLoginReqVO authLoginReqVO = new AuthLoginReqVO(); + authLoginReqVO.setUsername(authUser); + authLoginReqVO.setPassword((String) map.getOrDefault("bimRemotePwd", "")); + authLoginReqVO.setCaptchaVerification(encryptKey); + + try { + AuthLoginRespVO login = adminAuthService.login(authLoginReqVO); + OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(login.getAccessToken()).getCheckedData(); + if (accessToken == null) { + syncLogService.logAuthResult(logId, false); + throw exception(AUTH_LOGIN_USER_DISABLED); + } + + // 记录认证成功 + syncLogService.logAuthResult(logId, true); + + LoginUser loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) + .setInfo(accessToken.getUserInfo()) + .setTenantId(accessToken.getTenantId()) + .setScopes(accessToken.getScopes()) + .setExpiresTime(accessToken.getExpiresTime()); + SecurityFrameworkUtils.setLoginUser(loginUser, getRequest()); + + } catch (Exception e) { + syncLogService.logAuthResult(logId, false); + throw e; + } + + return JSON.parseObject(bodyJson, clazz); + + } catch (Exception e) { + // 如果是解密失败,记录解密失败 + if (bodyJson == null) { + syncLogService.logDecryptResult(logId, bimRequestId, null, authUser, false); + } + throw e; } - LoginUser loginUser = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType()) - .setInfo(accessToken.getUserInfo()) - .setTenantId(accessToken.getTenantId()) - .setScopes(accessToken.getScopes()) - .setExpiresTime(accessToken.getExpiresTime()); - SecurityFrameworkUtils.setLoginUser(loginUser, getRequest()); - return JSON.parseObject(bodyJson, clazz); } private String encrypt(T object) { @@ -302,4 +614,24 @@ public class SyncController { } return bodyJson; } + + /** + * 获取客户端IP地址 + */ + private String getClientIp() { + return getRequest().getRemoteAddr(); + } + + /** + * 获取异常堆栈信息 + */ + private String getStackTrace(Throwable throwable) { + if (throwable == null) { + return null; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + return sw.toString(); + } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncLogController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncLogController.java new file mode 100644 index 00000000..f3b2480a --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/SyncLogController.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.system.controller.admin.sync; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.controller.admin.sync.vo.SyncLogPageReqVO; +import cn.iocoder.yudao.module.system.controller.admin.sync.vo.SyncLogRespVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sync.SyncLogDO; +import cn.iocoder.yudao.module.system.service.sync.SyncLogService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * 同步接口日志 Controller + * + * @author 芋道源码 + */ +@Tag(name = "管理后台 - 同步接口日志") +@RestController +@RequestMapping("/system/sync-log") +@Validated +public class SyncLogController { + + @Resource + private SyncLogService syncLogService; + + @GetMapping("/get") + @Operation(summary = "获得同步接口日志") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:sync-log:query')") + public CommonResult getSyncLog(@RequestParam("id") Long id) { + SyncLogDO syncLog = syncLogService.getSyncLog(id); + return success(BeanUtils.toBean(syncLog, SyncLogRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得同步接口日志分页") + @PreAuthorize("@ss.hasPermission('system:sync-log:query')") + public CommonResult> getSyncLogPage(@Valid SyncLogPageReqVO pageReqVO) { + PageResult pageResult = syncLogService.getSyncLogPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, SyncLogRespVO.class)); + } + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogPageReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogPageReqVO.java new file mode 100644 index 00000000..7cc6127b --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogPageReqVO.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.system.controller.admin.sync.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +/** + * 同步接口日志分页 Request VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - 同步接口日志分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SyncLogPageReqVO extends PageParam { + + @Schema(description = "外部请求ID", example = "REQ_12345") + private String bimRequestId; + + @Schema(description = "服务名称", example = "SchemaService") + private String serviceName; + + @Schema(description = "响应状态", example = "0") + private Integer status; + + @Schema(description = "客户端IP", example = "192.168.1.100") + private String clientIp; + + @Schema(description = "认证用户", example = "admin") + private String authUser; + + @Schema(description = "请求时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] requestTime; + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogRespVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogRespVO.java new file mode 100644 index 00000000..daf35232 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/SyncLogRespVO.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.system.controller.admin.sync.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 同步接口日志 Response VO + * + * @author 芋道源码 + */ +@Schema(description = "管理后台 - 同步接口日志 Response VO") +@Data +public class SyncLogRespVO { + + @Schema(description = "日志主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "外部请求ID", example = "REQ_12345") + private String bimRequestId; + + @Schema(description = "服务名称", example = "SchemaService") + private String serviceName; + + @Schema(description = "请求方法", example = "POST") + private String requestMethod; + + @Schema(description = "请求URL", example = "/system/sync/SchemaService") + private String requestUrl; + + @Schema(description = "客户端IP地址", example = "192.168.1.100") + private String clientIp; + + @Schema(description = "用户代理字符串") + private String userAgent; + + @Schema(description = "请求开始时间", example = "2024-01-01 12:00:00") + private LocalDateTime requestTime; + + @Schema(description = "请求结束时间", example = "2024-01-01 12:00:01") + private LocalDateTime responseTime; + + @Schema(description = "请求处理耗时(毫秒)", example = "1000") + private Long duration; + + @Schema(description = "原始加密请求体") + private String encryptedRequest; + + @Schema(description = "解密后的请求体") + private String decryptedRequest; + + @Schema(description = "响应状态", example = "0") + private Integer status; + + @Schema(description = "错误码", example = "SYNC_001") + private String errorCode; + + @Schema(description = "错误信息", example = "解密失败") + private String errorMessage; + + @Schema(description = "异常堆栈") + private String exceptionStack; + + @Schema(description = "响应数据(加密前)") + private String responseData; + + @Schema(description = "加密后的响应数据") + private String encryptedResponse; + + @Schema(description = "认证用户", example = "admin") + private String authUser; + + @Schema(description = "解密状态", example = "0") + private Integer decryptStatus; + + @Schema(description = "签名验证状态", example = "0") + private Integer signatureVerifyStatus; + + @Schema(description = "认证状态", example = "0") + private Integer authStatus; + + @Schema(description = "业务处理结果", example = "200") + private String businessResult; + + @Schema(description = "额外信息") + private String extra; + + @Schema(description = "创建时间", example = "2024-01-01 12:00:00") + private LocalDateTime createTime; + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgCreateRequestVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgCreateRequestVO.java index 013c4491..101ea3ee 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgCreateRequestVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgCreateRequestVO.java @@ -24,8 +24,6 @@ public class OrgCreateRequestVO { private String email; @Schema(description = "机构 状态", required = true) private Integer status; - @Schema(description = "机构 是否公司") - private Boolean isCompany; - @Schema(description = "机构 是否集团") - private Boolean isGroup; + @Schema(description = "机构类型", required = true, example = "28=单位、26=部门") + private Integer deptType; } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgUpdateRequestVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgUpdateRequestVO.java index e8ff5170..d108cc02 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgUpdateRequestVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sync/vo/org/OrgUpdateRequestVO.java @@ -30,8 +30,8 @@ public class OrgUpdateRequestVO extends DeptDO { private String email; @Schema(description = "机构 状态", required = true) private Integer status; - @Schema(description = "机构 是否公司") - private Boolean isCompany; + @Schema(description = "机构类型", required = true, example = "28=单位、26=部门") + private Integer deptType; @Schema(description = "机构 是否集团") private Boolean isGroup; } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index 6d8e7132..4500da28 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -40,7 +40,7 @@ public class UserSaveReqVO { @DiffLogField(name = "备注") private String remark; - @Schema(description = "部门编号", example = "我是一个用户") + @Schema(description = "部门编号数组",requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @DiffLogField(name = "部门", function = DeptParseFunction.NAME) private Set deptIds; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sync/SyncLogDO.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sync/SyncLogDO.java new file mode 100644 index 00000000..a1ade20e --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/sync/SyncLogDO.java @@ -0,0 +1,166 @@ +package cn.iocoder.yudao.module.system.dal.dataobject.sync; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDateTime; + +/** + * 同步接口调用日志表 + * + * 用于记录外部系统调用同步接口的详细信息,包括加密和解密的过程 + * + * @author 芋道源码 + */ +@TableName("system_sync_log") +@KeySequence("system_sync_log_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class SyncLogDO extends BaseDO { + + /** + * 日志主键 + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 外部请求ID + * 从请求参数中的 bimRequestId 获取 + */ + private String bimRequestId; + + /** + * 接口名称/服务名称 + * 如:SchemaService、OrgCreateService、UserCreateService 等 + */ + private String serviceName; + + /** + * 请求方法 + * 固定为 POST + */ + private String requestMethod; + + /** + * 请求URL + */ + private String requestUrl; + + /** + * 客户端IP地址 + */ + private String clientIp; + + /** + * 用户代理字符串 + */ + private String userAgent; + + /** + * 请求开始时间 + */ + private LocalDateTime requestTime; + + /** + * 请求结束时间 + */ + private LocalDateTime responseTime; + + /** + * 请求处理耗时(毫秒) + */ + private Long duration; + + /** + * 原始加密请求体 + * 存储完整的加密数据 + */ + private String encryptedRequest; + + /** + * 解密后的请求体 + * JSON 格式,如果解密失败则为空 + */ + private String decryptedRequest; + + /** + * 响应状态 + * 0-成功, 1-解密失败, 2-签名验证失败, 3-认证失败, 4-业务处理失败, 5-系统异常 + */ + private Integer status; + + /** + * 错误码 + * 成功时为空,失败时记录具体错误码 + */ + private String errorCode; + + /** + * 错误信息 + * 成功时为空,失败时记录具体错误信息 + */ + private String errorMessage; + + /** + * 异常堆栈 + * 发生异常时记录完整的堆栈信息 + */ + private String exceptionStack; + + /** + * 响应数据(加密前) + * JSON 格式的响应数据 + */ + private String responseData; + + /** + * 加密后的响应数据 + * 返回给客户端的加密数据 + */ + private String encryptedResponse; + + /** + * 认证用户 + * 从请求中解析的 bimRemoteUser + */ + private String authUser; + + /** + * 解密状态 + * 0-成功, 1-失败 + */ + private Integer decryptStatus; + + /** + * 签名验证状态 + * 0-成功, 1-失败 + */ + private Integer signatureVerifyStatus; + + /** + * 认证状态 + * 0-成功, 1-失败 + */ + private Integer authStatus; + + /** + * 业务处理结果 + * 从业务响应中的 resultCode 获取 + */ + private String businessResult; + + /** + * 额外信息 + * JSON格式,存储一些额外的业务信息 + */ + private String extra; + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sync/SyncLogMapper.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sync/SyncLogMapper.java new file mode 100644 index 00000000..bafdfc4f --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/sync/SyncLogMapper.java @@ -0,0 +1,29 @@ +package cn.iocoder.yudao.module.system.dal.mysql.sync; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.system.dal.dataobject.sync.SyncLogDO; +import cn.iocoder.yudao.module.system.controller.admin.sync.vo.SyncLogPageReqVO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 同步接口日志 Mapper + * + * @author 芋道源码 + */ +@Mapper +public interface SyncLogMapper extends BaseMapperX { + + default PageResult selectPage(SyncLogPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(SyncLogDO::getBimRequestId, reqVO.getBimRequestId()) + .eqIfPresent(SyncLogDO::getServiceName, reqVO.getServiceName()) + .eqIfPresent(SyncLogDO::getStatus, reqVO.getStatus()) + .eqIfPresent(SyncLogDO::getClientIp, reqVO.getClientIp()) + .eqIfPresent(SyncLogDO::getAuthUser, reqVO.getAuthUser()) + .betweenIfPresent(SyncLogDO::getRequestTime, reqVO.getRequestTime()) + .orderByDesc(SyncLogDO::getId)); + } + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/enums/sync/SyncLogStatusEnum.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/enums/sync/SyncLogStatusEnum.java new file mode 100644 index 00000000..525d0461 --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/enums/sync/SyncLogStatusEnum.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.system.enums.sync; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 同步接口日志状态枚举 + * + * @author 芋道源码 + */ +@AllArgsConstructor +@Getter +public enum SyncLogStatusEnum { + + SUCCESS(0, "成功"), + DECRYPT_FAILED(1, "解密失败"), + SIGNATURE_VERIFY_FAILED(2, "签名验证失败"), + AUTH_FAILED(3, "认证失败"), + BUSINESS_FAILED(4, "业务处理失败"), + SYSTEM_ERROR(5, "系统异常"); + + /** + * 状态码 + */ + private final Integer status; + /** + * 状态描述 + */ + private final String description; + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/OrgSyncServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/OrgSyncServiceImpl.java index de9a0002..0f75def2 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/OrgSyncServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/OrgSyncServiceImpl.java @@ -1,19 +1,20 @@ package cn.iocoder.yudao.module.system.service.sync; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO; import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO; import cn.iocoder.yudao.module.system.controller.admin.sync.vo.org.*; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; +import cn.iocoder.yudao.module.system.enums.dept.DeptTypeEnum; import cn.iocoder.yudao.module.system.service.dept.DeptService; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.beans.PropertyDescriptor; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; + +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; /** * 统一机构相关 Service 实现 @@ -27,6 +28,12 @@ public class OrgSyncServiceImpl implements OrgSyncService { @Override public OrgCreateResponseVO createOrg(OrgCreateRequestVO requestVO) { DeptSaveReqVO bean = BeanUtils.toBean(requestVO, DeptSaveReqVO.class); + // 根据机构类型设置isCompany字段 + if (requestVO.getDeptType() != null) { + bean.setIsCompany(DeptTypeEnum.isCompany(requestVO.getDeptType())); + } + LoginUser loginUser = getLoginUser(); + bean.setTenantId(Optional.ofNullable(loginUser).orElse(new LoginUser()).getTenantId()); Long deptId = deptService.createDept(bean); OrgCreateResponseVO resp = new OrgCreateResponseVO(); resp.setBimRequestId(requestVO.getBimRequestId()); @@ -80,8 +87,8 @@ public class OrgSyncServiceImpl implements OrgSyncService { if (requestVO.getStatus() != null) { updateReqVO.setStatus(requestVO.getStatus()); } - if (requestVO.getIsCompany() != null) { - updateReqVO.setIsCompany(requestVO.getIsCompany()); + if (requestVO.getDeptType() != null) { + updateReqVO.setIsCompany(DeptTypeEnum.isCompany(requestVO.getDeptType())); } if (requestVO.getIsGroup() != null) { updateReqVO.setIsGroup(requestVO.getIsGroup()); diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogService.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogService.java new file mode 100644 index 00000000..2cf101de --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogService.java @@ -0,0 +1,102 @@ +package cn.iocoder.yudao.module.system.service.sync; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.sync.vo.SyncLogPageReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sync.SyncLogDO; + +/** + * 同步接口日志 Service 接口 + * + * @author 芋道源码 + */ +public interface SyncLogService { + + /** + * 创建同步接口日志 + * + * @param syncLog 同步日志信息 + * @return 日志编号 + */ + Long createSyncLog(SyncLogDO syncLog); + + /** + * 更新同步接口日志 + * + * @param syncLog 同步日志信息 + */ + void updateSyncLog(SyncLogDO syncLog); + + /** + * 获得同步接口日志 + * + * @param id 编号 + * @return 同步接口日志 + */ + SyncLogDO getSyncLog(Long id); + + /** + * 获得同步接口日志分页 + * + * @param pageReqVO 分页查询 + * @return 同步接口日志分页 + */ + PageResult getSyncLogPage(SyncLogPageReqVO pageReqVO); + + /** + * 记录请求开始 + * + * @param serviceName 服务名称 + * @param requestUrl 请求URL + * @param clientIp 客户端IP + * @param userAgent 用户代理 + * @param encryptedRequest 加密请求体 + * @return 日志ID + */ + Long logRequestStart(String serviceName, String requestUrl, String clientIp, + String userAgent, String encryptedRequest); + + /** + * 记录解密结果 + * + * @param logId 日志ID + * @param bimRequestId 外部请求ID + * @param decryptedRequest 解密后的请求体 + * @param authUser 认证用户 + * @param success 解密是否成功 + */ + void logDecryptResult(Long logId, String bimRequestId, String decryptedRequest, + String authUser, boolean success); + + /** + * 记录签名验证结果 + * + * @param logId 日志ID + * @param success 验证是否成功 + */ + void logSignatureVerifyResult(Long logId, boolean success); + + /** + * 记录认证结果 + * + * @param logId 日志ID + * @param success 认证是否成功 + */ + void logAuthResult(Long logId, boolean success); + + /** + * 记录请求完成 + * + * @param logId 日志ID + * @param status 最终状态 + * @param errorCode 错误码 + * @param errorMessage 错误信息 + * @param exceptionStack 异常堆栈 + * @param responseData 响应数据 + * @param encryptedResponse 加密响应 + * @param businessResult 业务结果 + */ + void logRequestComplete(Long logId, Integer status, String errorCode, String errorMessage, + String exceptionStack, String responseData, String encryptedResponse, + String businessResult); + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogServiceImpl.java new file mode 100644 index 00000000..50d6270e --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/SyncLogServiceImpl.java @@ -0,0 +1,148 @@ +package cn.iocoder.yudao.module.system.service.sync; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.controller.admin.sync.vo.SyncLogPageReqVO; +import cn.iocoder.yudao.module.system.dal.dataobject.sync.SyncLogDO; +import cn.iocoder.yudao.module.system.dal.mysql.sync.SyncLogMapper; +import cn.iocoder.yudao.module.system.enums.sync.SyncLogStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; + +/** + * 同步接口日志 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +@Slf4j +public class SyncLogServiceImpl implements SyncLogService { + + @Resource + private SyncLogMapper syncLogMapper; + + @Override + public Long createSyncLog(SyncLogDO syncLog) { + syncLogMapper.insert(syncLog); + return syncLog.getId(); + } + + @Override + public void updateSyncLog(SyncLogDO syncLog) { + syncLogMapper.updateById(syncLog); + } + + @Override + public SyncLogDO getSyncLog(Long id) { + return syncLogMapper.selectById(id); + } + + @Override + public PageResult getSyncLogPage(SyncLogPageReqVO pageReqVO) { + return syncLogMapper.selectPage(pageReqVO); + } + + @Override + public Long logRequestStart(String serviceName, String requestUrl, String clientIp, + String userAgent, String encryptedRequest) { + SyncLogDO syncLog = new SyncLogDO(); + syncLog.setServiceName(serviceName); + syncLog.setRequestMethod("POST"); + syncLog.setRequestUrl(requestUrl); + syncLog.setClientIp(clientIp); + syncLog.setUserAgent(userAgent); + syncLog.setRequestTime(LocalDateTime.now()); + syncLog.setEncryptedRequest(encryptedRequest); + syncLog.setStatus(SyncLogStatusEnum.SUCCESS.getStatus()); // 初始状态设为成功 + syncLog.setDecryptStatus(0); + syncLog.setSignatureVerifyStatus(0); + syncLog.setAuthStatus(0); + + return createSyncLog(syncLog); + } + + @Override + public void logDecryptResult(Long logId, String bimRequestId, String decryptedRequest, + String authUser, boolean success) { + SyncLogDO syncLog = new SyncLogDO(); + syncLog.setId(logId); + syncLog.setBimRequestId(bimRequestId); + syncLog.setDecryptedRequest(decryptedRequest); + syncLog.setAuthUser(authUser); + syncLog.setDecryptStatus(success ? 0 : 1); + + if (!success) { + syncLog.setStatus(SyncLogStatusEnum.DECRYPT_FAILED.getStatus()); + syncLog.setErrorCode("DECRYPT_ERROR"); + syncLog.setErrorMessage("请求解密失败"); + } + + updateSyncLog(syncLog); + } + + @Override + public void logSignatureVerifyResult(Long logId, boolean success) { + SyncLogDO syncLog = new SyncLogDO(); + syncLog.setId(logId); + syncLog.setSignatureVerifyStatus(success ? 0 : 1); + + if (!success) { + syncLog.setStatus(SyncLogStatusEnum.SIGNATURE_VERIFY_FAILED.getStatus()); + syncLog.setErrorCode("SIGNATURE_ERROR"); + syncLog.setErrorMessage("签名验证失败"); + } + + updateSyncLog(syncLog); + } + + @Override + public void logAuthResult(Long logId, boolean success) { + SyncLogDO syncLog = new SyncLogDO(); + syncLog.setId(logId); + syncLog.setAuthStatus(success ? 0 : 1); + + if (!success) { + syncLog.setStatus(SyncLogStatusEnum.AUTH_FAILED.getStatus()); + syncLog.setErrorCode("AUTH_ERROR"); + syncLog.setErrorMessage("用户认证失败"); + } + + updateSyncLog(syncLog); + } + + @Override + public void logRequestComplete(Long logId, Integer status, String errorCode, String errorMessage, + String exceptionStack, String responseData, String encryptedResponse, + String businessResult) { + SyncLogDO originalLog = getSyncLog(logId); + if (originalLog == null) { + log.warn("同步日志不存在,logId: {}", logId); + return; + } + + SyncLogDO syncLog = new SyncLogDO(); + syncLog.setId(logId); + syncLog.setResponseTime(LocalDateTime.now()); + + // 计算耗时 + if (originalLog.getRequestTime() != null) { + long duration = java.time.Duration.between(originalLog.getRequestTime(), syncLog.getResponseTime()).toMillis(); + syncLog.setDuration(duration); + } + + syncLog.setStatus(status); + syncLog.setErrorCode(errorCode); + syncLog.setErrorMessage(errorMessage); + syncLog.setExceptionStack(exceptionStack); + syncLog.setResponseData(responseData); + syncLog.setEncryptedResponse(encryptedResponse); + syncLog.setBusinessResult(businessResult); + + updateSyncLog(syncLog); + } + +} diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/UserSyncServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/UserSyncServiceImpl.java index 2562d6dc..41b0a66f 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/UserSyncServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/sync/UserSyncServiceImpl.java @@ -1,20 +1,21 @@ package cn.iocoder.yudao.module.system.service.sync; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.system.controller.admin.sync.vo.user.*; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.dataobject.userdept.UserDeptDO; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import cn.iocoder.yudao.module.system.service.userdept.UserDeptService; +import cn.iocoder.yudao.module.system.util.sync.SyncVerifyUtil; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.beans.PropertyDescriptor; -import java.util.*; - -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Service public class UserSyncServiceImpl implements UserSyncService { @@ -25,8 +26,11 @@ public class UserSyncServiceImpl implements UserSyncService { @Override public UserCreateResponseVO createUser(UserCreateRequestVO requestVO) { - TenantContextHolder.setTenantId(Objects.requireNonNull(getLoginUser()).getTenantId()); UserSaveReqVO saveReqVO = BeanUtils.toBean(requestVO, UserSaveReqVO.class); + // 转换外部性别编码为内部性别编码 + if (requestVO.getSex() != null) { + saveReqVO.setSex(SyncVerifyUtil.convertExternalToInternal(requestVO.getSex())); + } Long userId = adminUserService.createUser(saveReqVO); UserCreateResponseVO resp = new UserCreateResponseVO(); resp.setUid(String.valueOf(userId)); @@ -91,7 +95,8 @@ public class UserSyncServiceImpl implements UserSyncService { updateReqVO.setMobile(requestVO.getMobile()); } if (requestVO.getSex() != null) { - updateReqVO.setSex(requestVO.getSex()); + // 转换外部性别编码为内部性别编码 + updateReqVO.setSex(SyncVerifyUtil.convertExternalToInternal(requestVO.getSex())); } if (requestVO.getAvatar() != null && !requestVO.getAvatar().isEmpty()) { updateReqVO.setAvatar(requestVO.getAvatar()); @@ -124,6 +129,10 @@ public class UserSyncServiceImpl implements UserSyncService { } try { Object value = pd.getReadMethod().invoke(user); + // 如果是性别字段,需要转换为外部编码 + if ("sex".equals(name) && value instanceof Integer) { + value = SyncVerifyUtil.convertInternalToExternal((Integer) value); + } account.put(name, value); } catch (Exception ignore) {} } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index 4b619690..d9915fa2 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; +import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; @@ -44,6 +45,7 @@ import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*; @@ -101,6 +103,11 @@ public class AdminUserServiceImpl implements AdminUserService { // 2.1 插入用户 AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class); user.setStatus(CommonStatusEnum.ENABLE.getStatus()); + // 如果 tenantId 为空,则从上下文中获取 + if (user.getTenantId() == null) { + LoginUser loginUser = getLoginUser(); + user.setTenantId(Optional.ofNullable(loginUser).orElse(new LoginUser()).getTenantId()); + } user.setPassword(encodePassword(createReqVO.getPassword())); userMapper.insert(user); // 2.2 插入关联部门 diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtil.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtil.java index 1dc822e7..e0954ac5 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtil.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtil.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.module.system.util.sync; import cn.hutool.crypto.SecureUtil; -import cn.hutool.crypto.symmetric.AES; import cn.hutool.crypto.symmetric.DES; +import cn.iocoder.yudao.module.system.enums.common.SexEnum; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; @@ -17,6 +17,7 @@ import java.util.TreeMap; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.AUTH_LOGIN_BAD_CREDENTIALS; +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SYNC_DECRYPT_TYPE; /** * @author chenbowen @@ -31,7 +32,7 @@ public class SyncVerifyUtil { byte[] result = cipher.doFinal(Base64.getDecoder().decode(ciphertext.getBytes())); return new String(result, StandardCharsets.UTF_8); } catch (Exception e) { - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + throw exception(SYNC_DECRYPT_TYPE); } } else if ("DES".equalsIgnoreCase(type)) { byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8); @@ -42,7 +43,7 @@ public class SyncVerifyUtil { byte[] encryptedBytes = Base64.getDecoder().decode(ciphertext); return new String(des.decrypt(encryptedBytes), StandardCharsets.UTF_8); } else { - throw exception(AUTH_LOGIN_BAD_CREDENTIALS); + throw exception(SYNC_DECRYPT_TYPE); } } @@ -122,4 +123,42 @@ public class SyncVerifyUtil { } return provided.equalsIgnoreCase(computed); } + + /** + * e 办性别编码转换为内部性别编码 + * 外部:女=0,男=1 + * 内部:未知=0,男=1,女=2 + */ + public static Integer convertExternalToInternal(Integer externalSex) { + if (externalSex == null) { + return null; + } + switch (externalSex) { + case 0: // 外部:女 + return SexEnum.FEMALE.getSex(); // 内部:女=2 + case 1: // 外部:男 + return SexEnum.MALE.getSex(); // 内部:男=1 + default: + return SexEnum.UNKNOWN.getSex(); // 内部:未知=0 + } + } + + /** + * 内部性别编码转换为外部性别编码 + * 内部:未知=0,男=1,女=2 + * 外部:女=0,男=1 + */ + public static Integer convertInternalToExternal(Integer internalSex) { + if (internalSex == null) { + return null; + } + if (SexEnum.MALE.getSex().equals(internalSex)) { // 内部:男=1 + return 1; // 外部:男=1 + } else if (SexEnum.FEMALE.getSex().equals(internalSex)) { // 内部:女=2 + return 0; // 外部:女=0 + } else { + // 内部:未知=0,默认转换为外部女=0 + return 0; + } + } } diff --git a/yudao-module-system/yudao-module-system-server/src/main/resources/application.yaml b/yudao-module-system/yudao-module-system-server/src/main/resources/application.yaml index 6979f8ce..cd701253 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/resources/application.yaml +++ b/yudao-module-system/yudao-module-system-server/src/main/resources/application.yaml @@ -187,6 +187,7 @@ yudao: - system_seq - system_seq_dtl - system_seq_rcd + - system_sync_log ignore-caches: - user_role_ids - permission_menu_ids diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtilTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtilTest.java new file mode 100644 index 00000000..f9dcf34c --- /dev/null +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/util/sync/SyncVerifyUtilTest.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.module.system.util.sync; + +import cn.iocoder.yudao.module.system.enums.common.SexEnum; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * {@link SyncVerifyUtil} 的单元测试类 + * + * @author chenbowen + */ +public class SyncVerifyUtilTest { + + @Test + public void testConvertExternalToInternal() { + // 外部女=0 -> 内部女=2 + assertEquals(SexEnum.FEMALE.getSex(), SyncVerifyUtil.convertExternalToInternal(0)); + + // 外部男=1 -> 内部男=1 + assertEquals(SexEnum.MALE.getSex(), SyncVerifyUtil.convertExternalToInternal(1)); + + // 其他值 -> 内部未知=0 + assertEquals(SexEnum.UNKNOWN.getSex(), SyncVerifyUtil.convertExternalToInternal(2)); + assertEquals(SexEnum.UNKNOWN.getSex(), SyncVerifyUtil.convertExternalToInternal(99)); + + // null值处理 + assertNull(SyncVerifyUtil.convertExternalToInternal(null)); + } + + @Test + public void testConvertInternalToExternal() { + // 内部男=1 -> 外部男=1 + assertEquals(1, SyncVerifyUtil.convertInternalToExternal(SexEnum.MALE.getSex())); + + // 内部女=2 -> 外部女=0 + assertEquals(0, SyncVerifyUtil.convertInternalToExternal(SexEnum.FEMALE.getSex())); + + // 内部未知=0 -> 外部女=0 + assertEquals(0, SyncVerifyUtil.convertInternalToExternal(SexEnum.UNKNOWN.getSex())); + + // null值处理 + assertNull(SyncVerifyUtil.convertInternalToExternal(null)); + } +} diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 6ee552b6..e39d323b 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -48,40 +48,20 @@ spring: primary: master datasource: master: - url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 - # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 连接的示例 - # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 - # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 - # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例 - # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 - # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 - # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 - username: jygk-test - password: Zgty@0527 - # username: sa # SQL Server 连接的示例 - # password: Yudao@2024 # SQL Server 连接的示例 - # username: SYSDBA # DM 连接的示例 - # password: SYSDBA001 # DM 连接的示例 - # username: root # OpenGauss 连接的示例 - # password: Yudao@2024 # OpenGauss 连接的示例 - slave: # 模拟从库,可根据自己需要修改 + url: jdbc:dm://localhost:5233?schema=RUOYI-VUE-PRO + username: SYSDBA + password: P@ssword25 + slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://172.16.46.247:4787/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true - username: jygk-test - password: Zgty@0527 -# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) -# url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro -# driver-class-name: com.taosdata.jdbc.rs.RestfulDriver -# username: root -# password: taosdata -# druid: -# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL + url: jdbc:dm://localhost:5233?schema=RUOYI-VUE-PRO + username: SYSDBA + password: P@ssword25 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: redis: - host: 172.16.46.63 # 地址 - port: 30379 # 端口 + host: localhost # 地址 + port: 6379 # 端口 database: 0 # 数据库索引 # password: dev # 密码,建议生产环境开启 @@ -91,13 +71,13 @@ xxl: job: enabled: false # 是否开启调度中心,默认为 true 开启 admin: - addresses: http://172.16.46.63:30082/xxl-job-admin # 调度中心部署跟地址 + addresses: http://127.0.0.1:9090/xxl-job-admin # 调度中心部署跟地址 --- #################### 消息队列相关 #################### # rocketmq 配置项,对应 RocketMQProperties 配置类 rocketmq: - name-server: 172.16.46.63:30876 # RocketMQ Namesrv + name-server: localhost:9876 # RocketMQ Namesrv spring: # RabbitMQ 配置项,对应 RabbitProperties 配置类 @@ -221,10 +201,6 @@ yudao: wxa-subscribe-message: miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”;体验版为 “trial”为;正式版为 “formal” tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc - AES: - key: "0123456789abcdef0123456789abcdef" - verify-code: 666666 - justauth: enabled: true diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 26d69aab..5e5a67d5 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -268,6 +268,7 @@ yudao: security: permit-all_urls: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + - /admin-api/infra/onlyoffice/** websocket: enable: true # websocket的开关 path: /infra/ws # 路径 @@ -304,6 +305,12 @@ yudao: - /admin-api/system/user/profile/** - /admin-api/system/auth/** ignore-tables: + - system_seq + - system_seq_dtl + - system_seq_rcd + - system_sync_log + - infra_doc_file + - infra_doc_file_version ignore-caches: - user_role_ids - permission_menu_ids