# 操作审计日志 - 使用指南 > **📅 创建日期**:2026-06-16 > **⚠️ 重要提示**:异步线程中无法获取HTTP请求信息,必须在Controller层填充 --- ## 🔧 核心问题与解决方案 ### 问题 `OperationAuditLogService`使用`@Async`异步记录日志,在异步线程中无法通过`RequestContextHolder`获取HTTP请求信息(IP、User-Agent等)。 ### 解决方案 提供两种使用方式: 1. **推荐方式**:使用`logWithRequestInfo()`方法,自动在同步线程中获取请求信息 2. **手动方式**:使用静态方法`getClientIpFromRequest()`和`getUserAgentFromRequest()`手动获取 --- ## ✅ 推荐用法(最简单) ### Controller层示例 ```java @RestController @RequestMapping("/api/admin/user-level-config") @RequiredArgsConstructor public class UserLevelConfigController { private final OperationAuditLogService auditLogService; /** * 创建等级配置版本 */ @PostMapping("/version") public Result createVersion(@RequestBody CreateVersionRequest request, @AuthenticationPrincipal UserDetails userDetails) { Long versionId = userLevelConfigService.createVersion(request); // ✅ 推荐用法:一行代码搞定 auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operatorId(userDetails.getUserId()) .operatorName(userDetails.getNickname()) .operatorRole("ADMIN") .operationType(OperationType.LEVEL_VERSION_CREATE.getCode()) .module("等级配置") .description("创建等级配置版本: " + request.getVersion()) .targetType("LEVEL_CONFIG") .targetId(versionId) .targetIdentifier(request.getVersion()) .result("SUCCESS") .isSensitive(false) .remark("版本号: " + request.getVersion()) ); return Result.success(versionId); } } ``` **优点**: - ✅ 自动获取IP、User-Agent、Request URL、Request Method - ✅ 代码简洁,一行搞定 - ✅ 不会遗漏请求信息 --- ## 📝 手动用法(灵活控制) ### 场景1:需要自定义部分字段 ```java @PostMapping("/bind") public Result bindAccount(@RequestBody BindAccountRequest request, @AuthenticationPrincipal UserDetails userDetails) { platformAccountService.bindAccount(userDetails.getUserId(), request); // 手动构建builder AuditLogBuilder builder = AuditLogBuilder.builder() .operatorId(userDetails.getUserId()) .operatorName(userDetails.getNickname()) .operatorRole("USER") .operationType(OperationType.ACCOUNT_BIND.getCode()) .module("平台账号") .description("绑定" + request.getPlatformName() + "账号") .targetType("ACCOUNT") .targetIdentifier(request.getPlatformCode()) .result("SUCCESS"); // ✅ 手动填充请求信息 builder.ipAddress(OperationAuditLogService.getClientIpFromRequest()); builder.userAgent(OperationAuditLogService.getUserAgentFromRequest()); // 异步记录 auditLogService.logOperation(builder); return Result.success(); } ``` ### 场景2:记录成功操作(快捷方法) ```java @PostMapping("/migrate-users") public Result batchMigrateUsers(@RequestBody MigrateUsersRequest request, @AuthenticationPrincipal UserDetails userDetails) { int count = userLevelConfigService.batchMigrateUsers( request.getTargetVersionId(), request.getUserIds(), userDetails.getUserId(), request.getRemark() ); // ✅ 使用快捷方法 auditLogService.logSuccess( userDetails.getUserId(), userDetails.getNickname(), "ADMIN", OperationType.LEVEL_USER_BATCH_MIGRATE, "批量迁移用户", null, // targetId null, // beforeData Map.of("migratedCount", count), // afterData request.getRemark() ); // 注意:快捷方法不会自动填充请求信息,需要手动调用logWithRequestInfo或手动填充 return Result.success(count); } ``` ### 场景3:记录失败操作 ```java try { platformAccountService.verifyAccount(userId, platformCode); } catch (Exception e) { // ✅ 记录失败操作 auditLogService.logFailure( userId, userName, "USER", OperationType.ACCOUNT_VERIFY, "验证" + platformName + "账号", null, e.getMessage() ); throw new BusinessException("账号验证失败: " + e.getMessage()); } ``` --- ## 🎯 完整示例 ### 示例1:敏感操作审计 ```java /** * 批量迁移用户到新版本(敏感操作) */ @PostMapping("/migrate-users") public Result batchMigrateUsers(@RequestBody MigrateUsersRequest request, @AuthenticationPrincipal UserDetails userDetails) { Long operatorId = userDetails.getUserId(); String operatorName = userDetails.getNickname(); try { // 执行迁移 int count = userLevelConfigService.batchMigrateUsers( request.getTargetVersionId(), request.getUserIds(), operatorId, request.getRemark() ); // ✅ 记录成功日志(敏感操作) auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operatorId(operatorId) .operatorName(operatorName) .operatorRole("ADMIN") .operationType(OperationType.LEVEL_USER_BATCH_MIGRATE.getCode()) .module("等级配置") .description("批量迁移" + count + "个用户到新版本") .targetType("USER") .targetId(null) .targetIdentifier("批量迁移") .beforeData(Map.of("userCount", request.getUserIds().size())) .afterData(Map.of("migratedCount", count)) .result("SUCCESS") .isSensitive(true) // ⚠️ 标记为敏感操作 .remark(request.getRemark()) ); return Result.success(count); } catch (Exception e) { // ✅ 记录失败日志 auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operatorId(operatorId) .operatorName(operatorName) .operatorRole("ADMIN") .operationType(OperationType.LEVEL_USER_BATCH_MIGRATE.getCode()) .module("等级配置") .description("批量迁移用户失败") .targetType("USER") .result("FAILED") .errorMessage(e.getMessage()) .isSensitive(true) // ⚠️ 敏感操作 ); throw new BusinessException("批量迁移失败: " + e.getMessage()); } } ``` ### 示例2:数据变更审计 ```java /** * 调整用户等级 */ @PutMapping("/user/{userId}/level") public Result adjustUserLevel(@PathVariable Long userId, @RequestBody AdjustLevelRequest request, @AuthenticationPrincipal UserDetails userDetails) { // 查询调整前的等级 UserLevelVersionRelation oldRelation = versionRelationMapper.selectByUserId(userId); String oldLevelCode = oldRelation.getLevelCode(); // 执行调整 userLevelConfigService.adjustUserLevel(userId, request.getNewLevelCode()); // ✅ 记录数据变更(包含前后快照) auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operatorId(userDetails.getUserId()) .operatorName(userDetails.getNickname()) .operatorRole("ADMIN") .operationType(OperationType.LEVEL_USER_ADJUST.getCode()) .module("等级配置") .description("调整用户等级: " + oldLevelCode + " → " + request.getNewLevelCode()) .targetType("USER") .targetId(userId) .targetIdentifier("用户ID: " + userId) .beforeData(Map.of( "levelCode", oldLevelCode, "configVersionId", oldRelation.getConfigVersionId() )) .afterData(Map.of( "levelCode", request.getNewLevelCode() )) .result("SUCCESS") .isSensitive(true) .remark(request.getReason()) ); return Result.success(); } ``` --- ## ⚠️ 常见错误 ### ❌ 错误1:在Service层直接调用logOperation ```java @Service public class UserService { @Autowired private OperationAuditLogService auditLogService; public void updateUser(Long userId, UserInfo userInfo) { // 业务逻辑... // ❌ 错误:在Service层调用,无法获取HTTP请求信息 auditLogService.logOperation( AuditLogBuilder.builder() .operatorId(userId) // ... ); } } ``` **正确做法**:在Controller层调用,或使用`logWithRequestInfo()` --- ### ❌ 错误2:忘记标记敏感操作 ```java // ❌ 错误:删除用户是敏感操作,但没有标记 auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operationType(OperationType.USER_DELETE.getCode()) .isSensitive(false) // ❌ 应该是true ); ``` **正确做法**: ```java // ✅ 正确:标记为敏感操作 auditLogService.logWithRequestInfo( AuditLogBuilder.builder() .operationType(OperationType.USER_DELETE.getCode()) .isSensitive(true) // ✅ 敏感操作 ); ``` --- ### ❌ 错误3:异步方法中尝试获取请求信息 ```java @Async public void asyncMethod() { // ❌ 错误:异步线程中RequestContextHolder为null String ip = OperationAuditLogService.getClientIpFromRequest(); // 返回"unknown" } ``` **正确做法**:在调用异步方法前获取并传递 ```java public void syncMethod() { // ✅ 在同步线程中获取 String ip = OperationAuditLogService.getClientIpFromRequest(); String userAgent = OperationAuditLogService.getUserAgentFromRequest(); // 传递给异步方法 asyncMethod(ip, userAgent); } @Async public void asyncMethod(String ip, String userAgent) { // 使用传入的参数 } ``` --- ## 📊 审计日志查询 ### 1. 查询用户的操作日志 ```java // 查询最近50条操作日志 List logs = auditLogService.getUserOperationLogs(userId, 50); ``` ### 2. 查询目标对象的操作历史 ```java // 查询某个等级配置版本的所有操作历史 List history = auditLogService.getTargetOperationHistory( "LEVEL_CONFIG", versionId, 100 ); ``` ### 3. 分页查询(运营后台) ```java Page page = auditLogService.queryAuditLogs( "等级配置", // module "LEVEL_VERSION_ACTIVATE", // operationType null, // operatorId startTime, // startTime endTime, // endTime true, // isSensitive(只查敏感操作) 1, // pageNum 20 // pageSize ); ``` ### 4. 查询敏感操作日志 ```java // 查询最近7天的敏感操作 LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7); List sensitiveLogs = auditLogService.getSensitiveLogs(sevenDaysAgo); ``` --- ## 🎯 最佳实践 ### 1. 统一在Controller层记录日志 ```java @RestController public class MyController { @Autowired private OperationAuditLogService auditLogService; @PostMapping("/xxx") public Result doSomething(...) { // 执行业务逻辑 service.doBusiness(); // ✅ 在Controller层记录日志 auditLogService.logWithRequestInfo(...); return Result.success(); } } ``` ### 2. 使用try-catch确保异常也被记录 ```java try { service.doBusiness(); // 记录成功 auditLogService.logWithRequestInfo(...result("SUCCESS")...); } catch (Exception e) { // 记录失败 auditLogService.logWithRequestInfo(...result("FAILED").errorMessage(e.getMessage())...); throw e; } ``` ### 3. 敏感操作必须标记 以下操作应标记为敏感操作(`isSensitive=true`): - 删除用户/数据 - 批量修改用户等级 - 调整配额 - 审核营业执照 - 发放优惠券/配额 - 修改系统配置 ### 4. 重要操作记录数据快照 对于数据变更操作,记录beforeData和afterData: ```java .beforeData(Map.of("oldValue", oldValue)) .afterData(Map.of("newValue", newValue)) ``` --- ## 📞 技术支持 如有问题,请联系开发团队。 --- **📌 总结**: - ✅ 推荐使用`logWithRequestInfo()`方法,自动获取请求信息 - ✅ 在Controller层调用,不要在Service层或异步方法中调用 - ✅ 敏感操作必须标记`isSensitive=true` - ✅ 重要操作记录数据快照(beforeData/afterData) - ❌ 避免在异步线程中尝试获取HTTP请求信息