feat: 添加异常日志功能并优化UI样式

refactor: 重构后端查询逻辑和API响应处理

fix: 修复用户角色更新和文件上传问题

test: 添加前端性能测试脚本和E2E测试用例

chore: 更新依赖版本和配置文件

docs: 添加环境检查脚本和测试文档

style: 统一表格标签样式和路由命名

perf: 优化前端页面加载速度和响应时间
This commit is contained in:
张翔
2026-03-24 13:32:20 +08:00
parent a97d317e4a
commit be5d5ede90
184 changed files with 11231 additions and 1903 deletions
@@ -3,7 +3,6 @@ package cn.novalon.manage.sys.config;
import cn.novalon.manage.sys.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
@@ -43,9 +42,8 @@ public class SecurityConfig {
.pathMatchers("/api/auth/**").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/ws/**").permitAll()
.pathMatchers(HttpMethod.GET, "/actuator/**").permitAll()
.anyExchange().authenticated()
)
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated())
.build();
}
}
@@ -14,14 +14,18 @@ public record CreateUserCommand(
Username username,
Password password,
Email email,
String nickname,
String phone,
Long roleId,
Integer status
) {
public static CreateUserCommand of(String username, String password, String email, Long roleId, Integer status) {
public static CreateUserCommand of(String username, String password, String email, String nickname, String phone, Long roleId, Integer status) {
return new CreateUserCommand(
Username.of(username),
Password.of(password),
Email.of(email),
nickname,
phone,
roleId,
status
);
@@ -12,9 +12,14 @@ public record UpdateUserCommand(
String password,
String email,
Long roleId,
Integer status
Integer status,
boolean clearRole
) {
public static UpdateUserCommand of(Long id, String username, String password, String email, Long roleId, Integer status) {
return new UpdateUserCommand(id, username, password, email, roleId, status);
return new UpdateUserCommand(id, username, password, email, roleId, status, false);
}
public static UpdateUserCommand of(Long id, String username, String password, String email, Long roleId, Integer status, boolean clearRole) {
return new UpdateUserCommand(id, username, password, email, roleId, status, clearRole);
}
}
@@ -14,7 +14,9 @@ public class SysUser extends BaseDomain {
private String username;
private String password;
private String nickname;
private String email;
private String phone;
private Long roleId;
private Integer status;
@@ -34,6 +36,14 @@ public class SysUser extends BaseDomain {
this.password = password;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getEmail() {
return email;
}
@@ -42,6 +52,14 @@ public class SysUser extends BaseDomain {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Long getRoleId() {
return roleId;
}
@@ -0,0 +1,47 @@
package cn.novalon.manage.sys.core.query;
/**
* 操作日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class OperationLogQuery {
private String username;
private String operation;
private String status;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getOperation() {
return operation;
}
public void setOperation(String operation) {
this.operation = operation;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,47 @@
package cn.novalon.manage.sys.core.query;
/**
* 异常日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysExceptionLogQuery {
private String username;
private String title;
private String exceptionName;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getExceptionName() {
return exceptionName;
}
public void setExceptionName(String exceptionName) {
this.exceptionName = exceptionName;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -0,0 +1,47 @@
package cn.novalon.manage.sys.core.query;
/**
* 登录日志查询对象
*
* @author 张翔
* @date 2026-03-13
*/
public class SysLoginLogQuery {
private String username;
private String ip;
private String status;
private String keyword;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getKeyword() {
return keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.core.repository;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -26,7 +27,7 @@ public interface IOperationLogRepository {
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest);
Mono<Long> count();
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.core.service;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.query.OperationLogQuery;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -17,7 +18,7 @@ public interface IOperationLogService {
Flux<OperationLog> findAll();
Mono<OperationLog> findById(Long id);
Flux<OperationLog> findByUsername(String username);
Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest);
Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest);
Mono<Long> count();
Mono<Long> countToday();
}
@@ -3,6 +3,7 @@ package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.query.OperationLogQuery;
import cn.novalon.manage.sys.core.repository.IOperationLogRepository;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import org.springframework.stereotype.Service;
@@ -48,8 +49,8 @@ public class OperationLogService implements IOperationLogService {
}
@Override
public Mono<PageResponse<OperationLog>> findOperationLogsByPage(PageRequest pageRequest) {
return logRepository.findOperationLogsByPage(pageRequest);
public Mono<PageResponse<OperationLog>> findByQueryWithPagination(OperationLogQuery query, PageRequest pageRequest) {
return logRepository.findByQueryWithPagination(query, pageRequest);
}
@Override
@@ -48,8 +48,7 @@ public class SysRoleService implements ISysRoleService {
SysRoleQuery query = new SysRoleQuery();
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
query.setRoleName(pageRequest.getKeyword());
query.setRoleKey(pageRequest.getKeyword());
query.setKeyword(pageRequest.getKeyword());
}
return roleRepository.findByQueryWithPagination(query, pageRequest);
@@ -2,7 +2,6 @@ package cn.novalon.manage.sys.core.service.impl;
import cn.novalon.manage.common.util.StatusConstants;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.query.SysUserQuery;
import cn.novalon.manage.common.dto.PageRequest;
import cn.novalon.manage.common.dto.PageResponse;
import cn.novalon.manage.sys.core.repository.ISysUserRepository;
@@ -59,14 +58,7 @@ public class SysUserService implements ISysUserService {
@Override
public Mono<PageResponse<SysUser>> findUsersByPage(PageRequest pageRequest) {
SysUserQuery query = new SysUserQuery();
if (pageRequest.getKeyword() != null && !pageRequest.getKeyword().isEmpty()) {
query.setUsername(pageRequest.getKeyword());
query.setEmail(pageRequest.getKeyword());
}
return userRepository.findByQueryWithPagination(query, pageRequest);
return userRepository.findByQueryWithPagination(null, pageRequest);
}
@Override
@@ -95,6 +87,8 @@ public class SysUserService implements ISysUserService {
user.setUsername(command.username().getValue());
user.setPassword(passwordEncoder.encode(command.password().getValue()));
user.setEmail(command.email().getValue());
user.setNickname(command.nickname());
user.setPhone(command.phone());
user.setRoleId(command.roleId());
user.setStatus(command.status() != null ? command.status() : StatusConstants.ENABLED);
user.setCreatedAt(LocalDateTime.now());
@@ -121,7 +115,9 @@ public class SysUserService implements ISysUserService {
if (command.email() != null) {
user.setEmail(command.email());
}
if (command.roleId() != null) {
if (command.clearRole()) {
user.setRoleId(null);
} else if (command.roleId() != null) {
user.setRoleId(command.roleId());
}
if (command.status() != null) {
@@ -1,7 +1,6 @@
package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
/**
* 菜单创建请求DTO
@@ -1,7 +1,5 @@
package cn.novalon.manage.sys.dto.request;
import jakarta.validation.constraints.NotBlank;
/**
* 角色更新请求DTO
*
@@ -16,6 +16,9 @@ public class UserRegisterRequest {
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
private String username;
@Size(max = 100, message = "昵称长度不能超过100")
private String nickname;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在6-100之间")
private String password;
@@ -23,6 +26,9 @@ public class UserRegisterRequest {
@Email(message = "邮箱格式不正确")
private String email;
@Size(max = 20, message = "手机号长度不能超过20")
private String phone;
public String getUsername() {
return username;
}
@@ -31,6 +37,14 @@ public class UserRegisterRequest {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getPassword() {
return password;
}
@@ -46,4 +60,12 @@ public class UserRegisterRequest {
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
@@ -15,6 +15,8 @@ public class UserUpdateRequest {
private Integer status;
private Long roleId;
private Boolean clearRole;
@Email(message = "邮箱格式不正确")
public String getEmail() {
@@ -40,4 +42,12 @@ public class UserUpdateRequest {
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Boolean getClearRole() {
return clearRole;
}
public void setClearRole(Boolean clearRole) {
this.clearRole = clearRole;
}
}
@@ -6,7 +6,10 @@ import cn.novalon.manage.sys.dto.response.AuthResponse;
import cn.novalon.manage.sys.security.JwtTokenProvider;
import cn.novalon.manage.sys.core.domain.SysUser;
import cn.novalon.manage.sys.core.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;
@@ -32,66 +35,117 @@ import java.util.stream.Collectors;
@Component
public class SysAuthHandler {
private final ISysUserService userService;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private static final Logger logger = LoggerFactory.getLogger(SysAuthHandler.class);
private final ISysUserService userService;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder,
JwtTokenProvider jwtTokenProvider) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
}
public SysAuthHandler(ISysUserService userService, PasswordEncoder passwordEncoder,
JwtTokenProvider jwtTokenProvider) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
this.jwtTokenProvider = jwtTokenProvider;
}
public Mono<ServerResponse> login(ServerRequest request) {
return request.bodyToMono(LoginRequest.class)
.filter(loginRequest -> loginRequest.getUsername() != null
&& !loginRequest.getUsername().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("用户名不能为空")))
.filter(loginRequest -> loginRequest.getPassword() != null
&& !loginRequest.getPassword().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("密码不能为空")))
.flatMap(loginRequest -> userService.findByUsername(loginRequest.getUsername())
.filter(user -> passwordEncoder.matches(loginRequest.getPassword(), user.getPassword()))
.filter(user -> 1 == user.getStatus())
.flatMap(user -> {
String token = jwtTokenProvider.generateToken(user.getUsername(), user.getId());
AuthResponse response = new AuthResponse(token, user.getId(), user.getUsername());
return ServerResponse.ok().bodyValue(response);
})
.switchIfEmpty(ServerResponse.status(HttpStatus.UNAUTHORIZED).build()))
.onErrorResume(WebExchangeBindException.class, ex -> {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", errorMessage,
"timestamp", LocalDateTime.now()));
})
.onErrorResume(IllegalArgumentException.class, ex -> {
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", ex.getMessage(),
"timestamp", LocalDateTime.now()));
});
}
public Mono<ServerResponse> login(ServerRequest request) {
return request.bodyToMono(LoginRequest.class)
.filter(loginRequest -> loginRequest.getUsername() != null
&& !loginRequest.getUsername().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("用户名不能为空")))
.filter(loginRequest -> loginRequest.getPassword() != null
&& !loginRequest.getPassword().trim().isEmpty())
.switchIfEmpty(Mono.error(new IllegalArgumentException("密码不能为空")))
.flatMap(loginRequest -> {
logger.info("用户登录请求: username={}", loginRequest.getUsername());
return userService.findByUsername(loginRequest.getUsername())
.flatMap(user -> {
if (!passwordEncoder.matches(loginRequest.getPassword(),
user.getPassword())) {
logger.warn("用户登录失败: username={}, reason=密码错误",
loginRequest.getUsername());
return Mono.error(new RuntimeException(
"用户名或密码错误"));
}
if (user.getStatus() != 1) {
logger.warn("用户登录失败: username={}, reason=用户已禁用",
loginRequest.getUsername());
return Mono.error(new RuntimeException(
"用户名或密码错误"));
}
String token = jwtTokenProvider.generateToken(
user.getUsername(), user.getId());
logger.info("用户登录成功: username={}, userId={}",
user.getUsername(), user.getId());
AuthResponse response = new AuthResponse(token,
user.getId(), user.getUsername());
return ServerResponse.ok().bodyValue(response);
})
.switchIfEmpty(Mono.defer(() -> {
logger.warn("用户登录失败: username={}, reason=用户不存在",
loginRequest.getUsername());
return Mono.error(new RuntimeException("用户名或密码错误"));
}));
})
.onErrorResume(WebExchangeBindException.class, ex -> {
String errorMessage = ex.getBindingResult().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
logger.warn("用户登录请求参数验证失败: {}", errorMessage);
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", errorMessage,
"timestamp", LocalDateTime.now()));
})
.onErrorResume(IllegalArgumentException.class, ex -> {
logger.warn("用户登录请求参数错误: {}", ex.getMessage());
return ServerResponse.badRequest().bodyValue(Map.of(
"code", HttpStatus.BAD_REQUEST.value(),
"message", ex.getMessage(),
"timestamp", LocalDateTime.now()));
})
.onErrorResume(RuntimeException.class, ex -> {
if ("用户名或密码错误".equals(ex.getMessage())) {
return ServerResponse.status(HttpStatus.UNAUTHORIZED)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Map.of(
"code", HttpStatus.UNAUTHORIZED.value(),
"message", "用户名或密码错误",
"timestamp", LocalDateTime.now()));
}
logger.error("用户登录发生未预期的错误", ex);
return Mono.error(ex);
});
}
public Mono<ServerResponse> register(ServerRequest request) {
return request.bodyToMono(UserRegisterRequest.class)
.flatMap(registerRequest -> {
SysUser user = new SysUser();
user.setUsername(registerRequest.getUsername());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEmail(registerRequest.getEmail());
return userService.findByUsername(registerRequest.getUsername())
.flatMap(existing -> Mono.<ServerResponse>error(new RuntimeException("用户名已存在")))
.switchIfEmpty(userService.createUser(user)
.flatMap(u -> ServerResponse.status(HttpStatus.CREATED).bodyValue(u)));
});
}
public Mono<ServerResponse> register(ServerRequest request) {
return request.bodyToMono(UserRegisterRequest.class)
.flatMap(registerRequest -> {
logger.info("用户注册请求: username={}, email={}",
registerRequest.getUsername(), registerRequest.getEmail());
SysUser user = new SysUser();
user.setUsername(registerRequest.getUsername());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEmail(registerRequest.getEmail());
return userService.findByUsername(registerRequest.getUsername())
.flatMap(existing -> {
logger.warn("用户注册失败: username={}, reason=用户名已存在",
registerRequest.getUsername());
return Mono.<ServerResponse>error(
new RuntimeException("用户名已存在"));
})
.switchIfEmpty(userService.createUser(user)
.flatMap(u -> {
logger.info("用户注册成功: username={}, userId={}",
u.getUsername(),
u.getId());
return ServerResponse
.status(HttpStatus.CREATED)
.bodyValue(u);
}));
});
}
public Mono<ServerResponse> logout(ServerRequest request) {
return ServerResponse.ok().build();
}
public Mono<ServerResponse> logout(ServerRequest request) {
return ServerResponse.ok().build();
}
}
@@ -1,6 +1,7 @@
package cn.novalon.manage.sys.handler.log;
import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.query.OperationLogQuery;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import cn.novalon.manage.common.dto.PageRequest;
import io.swagger.v3.oas.annotations.Operation;
@@ -49,9 +50,12 @@ public class OperationLogHandler {
public Mono<ServerResponse> getOperationLogsByPage(ServerRequest request) {
int page = Integer.parseInt(request.queryParam("page").orElse("0"));
int size = Integer.parseInt(request.queryParam("size").orElse("10"));
String sort = request.queryParam("sort").orElse("created_at");
String sort = request.queryParam("sort").orElse("createdAt");
String order = request.queryParam("order").orElse("desc");
String keyword = request.queryParam("keyword").orElse(null);
String username = request.queryParam("username").orElse(null);
String operation = request.queryParam("operation").orElse(null);
String status = request.queryParam("status").orElse(null);
PageRequest pageRequest = new PageRequest();
pageRequest.setPage(page);
@@ -60,7 +64,13 @@ public class OperationLogHandler {
pageRequest.setOrder(order);
pageRequest.setKeyword(keyword);
return logService.findOperationLogsByPage(pageRequest)
OperationLogQuery query = new OperationLogQuery();
query.setUsername(username);
query.setOperation(operation);
query.setStatus(status);
query.setKeyword(keyword);
return logService.findByQueryWithPagination(query, pageRequest)
.flatMap(response -> ServerResponse.ok().bodyValue(response));
}
@@ -93,6 +93,8 @@ public class SysUserHandler {
req.getUsername(),
req.getPassword(),
req.getEmail(),
req.getNickname(),
req.getPhone(),
null,
null
))
@@ -104,14 +106,19 @@ public class SysUserHandler {
public Mono<ServerResponse> updateUser(ServerRequest request) {
Long id = Long.valueOf(request.pathVariable("id"));
return request.bodyToMono(UserUpdateRequest.class)
.map(req -> UpdateUserCommand.of(
id,
null,
null,
req.getEmail(),
req.getRoleId(),
req.getStatus()
))
.map(req -> {
boolean clearRole = Boolean.TRUE.equals(req.getClearRole()) ||
(req.getRoleId() == null && req.getClearRole() != null);
return UpdateUserCommand.of(
id,
null,
null,
req.getEmail(),
req.getRoleId(),
req.getStatus(),
clearRole
);
})
.flatMap(userService::updateUser)
.flatMap(user -> ServerResponse.ok().bodyValue(user))
.switchIfEmpty(ServerResponse.notFound().build());
@@ -13,8 +13,6 @@ import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.time.Duration;
/**
* 操作日志过滤器
*
@@ -31,22 +29,23 @@ public class OperationLogFilter implements WebFilter {
private static final Logger logger = LoggerFactory.getLogger(OperationLogFilter.class);
private final IOperationLogService logService;
private final ObjectMapper objectMapper;
public OperationLogFilter(IOperationLogService logService, ObjectMapper objectMapper) {
this.logService = logService;
this.objectMapper = objectMapper;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
long startTime = System.currentTimeMillis();
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().value();
String method = request.getMethod().name();
String ip = getClientIp(request);
if (path.startsWith("/api/auth/")) {
return chain.filter(exchange);
}
return chain.filter(exchange)
.doOnSuccess(v -> {
long duration = System.currentTimeMillis() - startTime;
@@ -58,14 +57,15 @@ public class OperationLogFilter implements WebFilter {
});
}
private void recordLog(ServerWebExchange exchange, String path, String method, String ip, long duration, String errorMsg) {
private void recordLog(ServerWebExchange exchange, String path, String method, String ip, long duration,
String errorMsg) {
try {
OperationLog log = new OperationLog();
log.setOperation(path);
log.setMethod(method);
log.setIp(ip);
log.setDuration(duration);
if (errorMsg != null) {
log.setStatus("1");
log.setErrorMsg(errorMsg);
@@ -74,10 +74,10 @@ public class OperationLogFilter implements WebFilter {
log.setStatus("0");
log.setResult("Success");
}
String queryParams = exchange.getRequest().getQueryParams().toSingleValueMap().toString();
log.setParams(queryParams);
ReactiveSecurityContextHolder.getContext()
.flatMap(securityContext -> {
Object principal = securityContext.getAuthentication().getPrincipal();