From 73dd4814f273ee05071b42b3a71ac472eff9222d Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Wed, 27 May 2026 13:36:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(logs):=20=E6=B7=BB=E5=8A=A0=E6=93=8D?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E5=92=8C=E6=8E=88=E6=9D=83=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在JwtAuthFilter中设置认证详情到claims - 为license-service添加LicenseOperationLog相关实体、仓库和服务 - 为push-service添加PushOperationLog相关实体、仓库和服务 - 在LicenseAdminController中注入并使用操作日志记录授权变更 - 在PushManagementController中注入并使用操作日志记录推送操作 - 更新OperationLogService以支持从JWT claims获取用户信息 - 扩展OperationLogService支持推送和授权操作日志查询 - 在前端OperationLogView中添加推送和授权日志选项卡 - 添加LicenseOperationLog和PushOperationLog接口定义 - 实现推送和授权日志的数据加载和分页功能 - 添加操作类型和资源类型的标签映射支持 --- .../xuqm/common/security/JwtAuthFilter.java | 1 + .../controller/LicenseAdminController.java | 9 ++- .../LicenseOperationLogController.java | 37 ++++++++++ .../entity/LicenseOperationLogEntity.java | 69 +++++++++++++++++++ .../LicenseOperationLogRepository.java | 10 +++ .../service/LicenseOperationLogService.java | 56 +++++++++++++++ .../controller/PushManagementController.java | 21 +++++- .../PushOperationLogController.java | 39 +++++++++++ .../push/entity/PushOperationLogEntity.java | 69 +++++++++++++++++++ .../PushOperationLogRepository.java | 10 +++ .../push/service/PushOperationLogService.java | 58 ++++++++++++++++ .../tenant/service/OperationLogService.java | 6 ++ .../service/UpdateOperationLogService.java | 6 ++ 13 files changed, 388 insertions(+), 3 deletions(-) create mode 100644 license-service/src/main/java/com/xuqm/license/controller/LicenseOperationLogController.java create mode 100644 license-service/src/main/java/com/xuqm/license/entity/LicenseOperationLogEntity.java create mode 100644 license-service/src/main/java/com/xuqm/license/repository/LicenseOperationLogRepository.java create mode 100644 license-service/src/main/java/com/xuqm/license/service/LicenseOperationLogService.java create mode 100644 push-service/src/main/java/com/xuqm/push/controller/PushOperationLogController.java create mode 100644 push-service/src/main/java/com/xuqm/push/entity/PushOperationLogEntity.java create mode 100644 push-service/src/main/java/com/xuqm/push/repository/PushOperationLogRepository.java create mode 100644 push-service/src/main/java/com/xuqm/push/service/PushOperationLogService.java diff --git a/common/src/main/java/com/xuqm/common/security/JwtAuthFilter.java b/common/src/main/java/com/xuqm/common/security/JwtAuthFilter.java index 7d15921..f44380f 100644 --- a/common/src/main/java/com/xuqm/common/security/JwtAuthFilter.java +++ b/common/src/main/java/com/xuqm/common/security/JwtAuthFilter.java @@ -37,6 +37,7 @@ public class JwtAuthFilter extends OncePerRequestFilter { : List.of(); UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(subject, null, authorities); + auth.setDetails(claims); SecurityContextHolder.getContext().setAuthentication(auth); } } diff --git a/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java b/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java index 09f0ae7..725b9a9 100644 --- a/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java +++ b/license-service/src/main/java/com/xuqm/license/controller/LicenseAdminController.java @@ -5,6 +5,7 @@ import com.xuqm.license.entity.AppLicenseEntity; import com.xuqm.license.entity.DeviceEntity; import com.xuqm.license.service.AppLicenseService; import com.xuqm.license.service.DeviceService; +import com.xuqm.license.service.LicenseOperationLogService; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -17,10 +18,13 @@ public class LicenseAdminController { private final AppLicenseService appLicenseService; private final DeviceService deviceService; + private final LicenseOperationLogService opLogService; - public LicenseAdminController(AppLicenseService appLicenseService, DeviceService deviceService) { + public LicenseAdminController(AppLicenseService appLicenseService, DeviceService deviceService, + LicenseOperationLogService opLogService) { this.appLicenseService = appLicenseService; this.deviceService = deviceService; + this.opLogService = opLogService; } @GetMapping("/apps/{appKey}") @@ -52,18 +56,21 @@ public class LicenseAdminController { } AppLicenseEntity updated = appLicenseService.update( appKey, null, req.maxDevices(), newExpiresAt, clearExpiresAt, req.isActive(), req.remark(), null, null, null); + opLogService.record(appKey, "UPDATE_LICENSE", "LICENSE", appKey, "更新 " + appKey + " 的授权配置"); return ResponseEntity.ok(ApiResponse.success(updated)); } @DeleteMapping("/devices/{id}") public ResponseEntity> revokeDevice(@PathVariable String id) { deviceService.revoke(id); + opLogService.record(null, "REVOKE_DEVICE", "DEVICE", id, "吊销设备 " + id + " 的授权"); return ResponseEntity.ok(ApiResponse.ok()); } @PutMapping("/devices/{id}/reactivate") public ResponseEntity> reactivateDevice(@PathVariable String id) { deviceService.reactivate(id); + opLogService.record(null, "REACTIVATE_DEVICE", "DEVICE", id, "重新激活设备 " + id); return ResponseEntity.ok(ApiResponse.ok()); } diff --git a/license-service/src/main/java/com/xuqm/license/controller/LicenseOperationLogController.java b/license-service/src/main/java/com/xuqm/license/controller/LicenseOperationLogController.java new file mode 100644 index 0000000..113cfde --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/controller/LicenseOperationLogController.java @@ -0,0 +1,37 @@ +package com.xuqm.license.controller; + +import com.xuqm.common.model.ApiResponse; +import com.xuqm.license.entity.LicenseOperationLogEntity; +import com.xuqm.license.service.LicenseOperationLogService; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/license/admin/operation-logs") +public class LicenseOperationLogController { + + private final LicenseOperationLogService logService; + + public LicenseOperationLogController(LicenseOperationLogService logService) { + this.logService = logService; + } + + @GetMapping + public ResponseEntity>> list( + @RequestParam String appKey, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + Page result = logService.list(appKey, page, size); + return ResponseEntity.ok(ApiResponse.success(Map.of( + "content", result.getContent(), + "total", result.getTotalElements(), + "totalPages", result.getTotalPages() + ))); + } +} diff --git a/license-service/src/main/java/com/xuqm/license/entity/LicenseOperationLogEntity.java b/license-service/src/main/java/com/xuqm/license/entity/LicenseOperationLogEntity.java new file mode 100644 index 0000000..c89626f --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/entity/LicenseOperationLogEntity.java @@ -0,0 +1,69 @@ +package com.xuqm.license.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.time.LocalDateTime; + +@Entity +@Table(name = "license_operation_log", indexes = { + @Index(name = "idx_license_op_log_app_time", columnList = "appKey,createdAt") +}) +public class LicenseOperationLogEntity { + + @Id + private String id; + + @Column(nullable = false, length = 64) + private String appKey; + + @Column(nullable = false, length = 128) + private String operator; + + @Column(nullable = false, length = 64) + private String action; + + @Column(nullable = false, length = 64) + private String resourceType; + + @Column(length = 128) + private String resourceId; + + @Column(length = 255) + private String summary; + + @Column(columnDefinition = "TEXT") + private String detailJson; + + @Column(nullable = false) + private LocalDateTime createdAt; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + + public String getAction() { return action; } + public void setAction(String action) { this.action = action; } + + public String getResourceType() { return resourceType; } + public void setResourceType(String resourceType) { this.resourceType = resourceType; } + + public String getResourceId() { return resourceId; } + public void setResourceId(String resourceId) { this.resourceId = resourceId; } + + public String getSummary() { return summary; } + public void setSummary(String summary) { this.summary = summary; } + + public String getDetailJson() { return detailJson; } + public void setDetailJson(String detailJson) { this.detailJson = detailJson; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } +} diff --git a/license-service/src/main/java/com/xuqm/license/repository/LicenseOperationLogRepository.java b/license-service/src/main/java/com/xuqm/license/repository/LicenseOperationLogRepository.java new file mode 100644 index 0000000..8ef5216 --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/repository/LicenseOperationLogRepository.java @@ -0,0 +1,10 @@ +package com.xuqm.license.repository; + +import com.xuqm.license.entity.LicenseOperationLogEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LicenseOperationLogRepository extends JpaRepository { + Page findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable); +} diff --git a/license-service/src/main/java/com/xuqm/license/service/LicenseOperationLogService.java b/license-service/src/main/java/com/xuqm/license/service/LicenseOperationLogService.java new file mode 100644 index 0000000..ed533c2 --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/service/LicenseOperationLogService.java @@ -0,0 +1,56 @@ +package com.xuqm.license.service; + +import com.xuqm.license.entity.LicenseOperationLogEntity; +import com.xuqm.license.repository.LicenseOperationLogRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Service +public class LicenseOperationLogService { + + private final LicenseOperationLogRepository repository; + + public LicenseOperationLogService(LicenseOperationLogRepository repository) { + this.repository = repository; + } + + public void record(String appKey, String action, String resourceType, + String resourceId, String summary) { + LicenseOperationLogEntity entity = new LicenseOperationLogEntity(); + entity.setId(UUID.randomUUID().toString()); + entity.setAppKey(appKey); + entity.setOperator(currentOperator()); + entity.setAction(action); + entity.setResourceType(resourceType); + entity.setResourceId(resourceId); + entity.setSummary(summary); + entity.setCreatedAt(LocalDateTime.now()); + repository.save(entity); + } + + public Page list(String appKey, int page, int size) { + int safePage = Math.max(page, 0); + int safeSize = Math.min(Math.max(size, 1), 200); + return repository.findByAppKeyOrderByCreatedAtDesc(appKey, PageRequest.of(safePage, safeSize)); + } + + private String currentOperator() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null || auth.getName() == null || auth.getName().isBlank()) { + return "system"; + } + if (auth.getDetails() instanceof io.jsonwebtoken.Claims claims) { + String nickname = claims.get("nickname", String.class); + if (nickname != null && !nickname.isBlank()) return nickname; + String username = claims.get("username", String.class); + if (username != null && !username.isBlank()) return username; + } + return auth.getName(); + } +} diff --git a/push-service/src/main/java/com/xuqm/push/controller/PushManagementController.java b/push-service/src/main/java/com/xuqm/push/controller/PushManagementController.java index 5a49f54..0a32187 100644 --- a/push-service/src/main/java/com/xuqm/push/controller/PushManagementController.java +++ b/push-service/src/main/java/com/xuqm/push/controller/PushManagementController.java @@ -5,6 +5,8 @@ import com.xuqm.push.entity.DeviceLoginLogEntity; import com.xuqm.push.entity.PushUserEntity; import com.xuqm.push.service.PushAccountService; import com.xuqm.push.service.PushDiagnosticsService; +import com.xuqm.push.service.PushOperationLogService; +import java.util.LinkedHashMap; import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -28,11 +30,14 @@ public class PushManagementController { private final PushDiagnosticsService diagnosticsService; private final PushAccountService accountService; + private final PushOperationLogService opLogService; public PushManagementController(PushDiagnosticsService diagnosticsService, - PushAccountService accountService) { + PushAccountService accountService, + PushOperationLogService opLogService) { this.diagnosticsService = diagnosticsService; this.accountService = accountService; + this.opLogService = opLogService; } // ---- user account management ---- @@ -68,6 +73,8 @@ public class PushManagementController { } PushUserEntity updated = accountService.updateAccount(request.appKey(), userId, request.nickname(), request.avatar(), gender); + opLogService.record(request.appKey(), "UPDATE_USER", "ACCOUNT", userId, + "编辑用户 " + userId + " 的信息", null); return ResponseEntity.ok(ApiResponse.success(updated)); } @@ -81,7 +88,11 @@ public class PushManagementController { } catch (Exception e) { return ResponseEntity.badRequest().body(ApiResponse.error(400, "无效的状态值,可选:ACTIVE, BANNED")); } - return ResponseEntity.ok(ApiResponse.success(accountService.setUserStatus(request.appKey(), userId, status))); + PushUserEntity result = accountService.setUserStatus(request.appKey(), userId, status); + String statusLabel = status == PushUserEntity.Status.BANNED ? "禁用" : "启用"; + opLogService.record(request.appKey(), "UPDATE_USER_STATUS", "ACCOUNT", userId, + statusLabel + "用户 " + userId, null); + return ResponseEntity.ok(ApiResponse.success(result)); } @DeleteMapping("/users/{userId}") @@ -89,6 +100,8 @@ public class PushManagementController { @PathVariable String userId, @RequestParam String appKey) { accountService.deleteAccount(appKey, userId); + opLogService.record(appKey, "DELETE_USER", "ACCOUNT", userId, + "删除用户 " + userId, null); return ResponseEntity.ok(ApiResponse.ok()); } @@ -104,6 +117,8 @@ public class PushManagementController { } PushUserEntity user = accountService.importAccount(request.appKey(), request.userId(), request.nickname(), request.avatar(), gender, status); + opLogService.record(request.appKey(), "IMPORT_USER", "ACCOUNT", request.userId(), + "导入用户 " + request.userId(), null); return ResponseEntity.ok(ApiResponse.success(user)); } @@ -139,6 +154,8 @@ public class PushManagementController { request.title(), request.body(), request.payload()); + opLogService.record(request.appKey(), "TEST_PUSH", "PUSH", request.userId(), + "向 " + request.userId() + " 发送测试推送", null); return ResponseEntity.ok(ApiResponse.success(result)); } diff --git a/push-service/src/main/java/com/xuqm/push/controller/PushOperationLogController.java b/push-service/src/main/java/com/xuqm/push/controller/PushOperationLogController.java new file mode 100644 index 0000000..099b5a9 --- /dev/null +++ b/push-service/src/main/java/com/xuqm/push/controller/PushOperationLogController.java @@ -0,0 +1,39 @@ +package com.xuqm.push.controller; + +import com.xuqm.common.model.ApiResponse; +import com.xuqm.push.entity.PushOperationLogEntity; +import com.xuqm.push.service.PushOperationLogService; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/push/admin/operation-logs") +@PreAuthorize("hasAnyAuthority('ROLE_OPS', 'ROLE_TENANT', 'ROLE_ADMIN')") +public class PushOperationLogController { + + private final PushOperationLogService logService; + + public PushOperationLogController(PushOperationLogService logService) { + this.logService = logService; + } + + @GetMapping + public ResponseEntity>> list( + @RequestParam String appKey, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "20") int size) { + Page result = logService.list(appKey, page, size); + return ResponseEntity.ok(ApiResponse.success(Map.of( + "content", result.getContent(), + "total", result.getTotalElements(), + "totalPages", result.getTotalPages() + ))); + } +} diff --git a/push-service/src/main/java/com/xuqm/push/entity/PushOperationLogEntity.java b/push-service/src/main/java/com/xuqm/push/entity/PushOperationLogEntity.java new file mode 100644 index 0000000..8f08d0c --- /dev/null +++ b/push-service/src/main/java/com/xuqm/push/entity/PushOperationLogEntity.java @@ -0,0 +1,69 @@ +package com.xuqm.push.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import java.time.LocalDateTime; + +@Entity +@Table(name = "push_operation_log", indexes = { + @Index(name = "idx_push_op_log_app_time", columnList = "appKey,createdAt") +}) +public class PushOperationLogEntity { + + @Id + private String id; + + @Column(nullable = false, length = 64) + private String appKey; + + @Column(nullable = false, length = 128) + private String operator; + + @Column(nullable = false, length = 64) + private String action; + + @Column(nullable = false, length = 64) + private String resourceType; + + @Column(length = 128) + private String resourceId; + + @Column(length = 255) + private String summary; + + @Column(columnDefinition = "TEXT") + private String detail; + + @Column(nullable = false) + private LocalDateTime createdAt; + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + + public String getAppKey() { return appKey; } + public void setAppKey(String appKey) { this.appKey = appKey; } + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + + public String getAction() { return action; } + public void setAction(String action) { this.action = action; } + + public String getResourceType() { return resourceType; } + public void setResourceType(String resourceType) { this.resourceType = resourceType; } + + public String getResourceId() { return resourceId; } + public void setResourceId(String resourceId) { this.resourceId = resourceId; } + + public String getSummary() { return summary; } + public void setSummary(String summary) { this.summary = summary; } + + public String getDetail() { return detail; } + public void setDetail(String detail) { this.detail = detail; } + + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } +} diff --git a/push-service/src/main/java/com/xuqm/push/repository/PushOperationLogRepository.java b/push-service/src/main/java/com/xuqm/push/repository/PushOperationLogRepository.java new file mode 100644 index 0000000..56246cf --- /dev/null +++ b/push-service/src/main/java/com/xuqm/push/repository/PushOperationLogRepository.java @@ -0,0 +1,10 @@ +package com.xuqm.push.repository; + +import com.xuqm.push.entity.PushOperationLogEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PushOperationLogRepository extends JpaRepository { + Page findByAppKeyOrderByCreatedAtDesc(String appKey, Pageable pageable); +} diff --git a/push-service/src/main/java/com/xuqm/push/service/PushOperationLogService.java b/push-service/src/main/java/com/xuqm/push/service/PushOperationLogService.java new file mode 100644 index 0000000..e4c856a --- /dev/null +++ b/push-service/src/main/java/com/xuqm/push/service/PushOperationLogService.java @@ -0,0 +1,58 @@ +package com.xuqm.push.service; + +import com.xuqm.push.entity.PushOperationLogEntity; +import com.xuqm.push.repository.PushOperationLogRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.Map; +import java.util.UUID; + +@Service +public class PushOperationLogService { + + private final PushOperationLogRepository repository; + + public PushOperationLogService(PushOperationLogRepository repository) { + this.repository = repository; + } + + public void record(String appKey, String action, String resourceType, + String resourceId, String summary, Map detail) { + PushOperationLogEntity entity = new PushOperationLogEntity(); + entity.setId(UUID.randomUUID().toString()); + entity.setAppKey(appKey); + entity.setOperator(currentOperator()); + entity.setAction(action); + entity.setResourceType(resourceType); + entity.setResourceId(resourceId); + entity.setSummary(summary); + entity.setDetail(detail == null ? null : detail.toString()); + entity.setCreatedAt(LocalDateTime.now()); + repository.save(entity); + } + + public Page list(String appKey, int page, int size) { + int safePage = Math.max(page, 0); + int safeSize = Math.min(Math.max(size, 1), 200); + return repository.findByAppKeyOrderByCreatedAtDesc(appKey, PageRequest.of(safePage, safeSize)); + } + + private String currentOperator() { + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth == null || auth.getName() == null || auth.getName().isBlank()) { + return "system"; + } + if (auth.getDetails() instanceof io.jsonwebtoken.Claims claims) { + String nickname = claims.get("nickname", String.class); + if (nickname != null && !nickname.isBlank()) return nickname; + String username = claims.get("username", String.class); + if (username != null && !username.isBlank()) return username; + } + return auth.getName(); + } +} diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/OperationLogService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/OperationLogService.java index 788b49f..bc9d235 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/OperationLogService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/OperationLogService.java @@ -77,6 +77,12 @@ public class OperationLogService { if (auth == null || auth.getName() == null || auth.getName().isBlank()) { return "system"; } + if (auth.getDetails() instanceof io.jsonwebtoken.Claims claims) { + String nickname = claims.get("nickname", String.class); + if (nickname != null && !nickname.isBlank()) return nickname; + String username = claims.get("username", String.class); + if (username != null && !username.isBlank()) return username; + } return auth.getName(); } diff --git a/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java b/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java index 0772ca5..bd782d7 100644 --- a/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java +++ b/update-service/src/main/java/com/xuqm/update/service/UpdateOperationLogService.java @@ -56,6 +56,12 @@ public class UpdateOperationLogService { if (auth == null || auth.getName() == null || auth.getName().isBlank()) { return "system"; } + if (auth.getDetails() instanceof io.jsonwebtoken.Claims claims) { + String nickname = claims.get("nickname", String.class); + if (nickname != null && !nickname.isBlank()) return nickname; + String username = claims.get("username", String.class); + if (username != null && !username.isBlank()) return username; + } return auth.getName(); }