From 85d2b85b6a1a26ad7bd295821ef9583f0f5713b8 Mon Sep 17 00:00:00 2001 From: huangping Date: Mon, 25 May 2026 14:46:42 +0800 Subject: [PATCH] fix: add userId filter to audit search and login lockout logic --- .../platform/api/audit/AuditController.java | 6 ++- .../platform/api/auth/AuthController.java | 37 ++++++++++++++++++- .../platform/api/service/AuditService.java | 27 ++++++++------ 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/audit/AuditController.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/audit/AuditController.java index 9a44e22..469180f 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/audit/AuditController.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/audit/AuditController.java @@ -34,19 +34,21 @@ public class AuditController { public PageResponse list( @RequestParam(required = false) String entityType, @RequestParam(required = false) Long entityId, + @RequestParam(required = false) String userId, @RequestParam(value = "page", defaultValue = "0") @Min(0) int page, @RequestParam(value = "size", defaultValue = "20") @Min(1) @Max(200) int size) { - return auditService.page(entityType, entityId, page, size); + return auditService.page(entityType, entityId, userId, page, size); } @GetMapping("/export") public ResponseEntity exportAuditEvents( @RequestParam(required = false) String entityType, @RequestParam(required = false) Long entityId, + @RequestParam(required = false) String userId, @RequestParam(required = false) String from, @RequestParam(required = false) String to) { - List events = auditService.searchAuditEvents(entityType, entityId, from, to); + List events = auditService.searchAuditEvents(entityType, entityId, from, to, userId); StringBuilder sb = new StringBuilder(); sb.append("时间,操作者,动作,实体类型,实体ID,摘要,详情\n"); diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java index c069e61..19289aa 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/auth/AuthController.java @@ -1,7 +1,10 @@ package cn.craftlabs.platform.api.auth; +import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttempt; +import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttemptMapper; import cn.craftlabs.platform.api.security.JwtService; import cn.craftlabs.platform.api.security.PlatformRoles; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; @@ -20,10 +23,15 @@ public class AuthController { private final JwtService jwtService; private final PasswordEncoder passwordEncoder; + private final PlatformLoginAttemptMapper loginAttemptMapper; + private final HttpServletRequest request; - public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder) { + public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder, + PlatformLoginAttemptMapper loginAttemptMapper, HttpServletRequest request) { this.jwtService = jwtService; this.passwordEncoder = passwordEncoder; + this.loginAttemptMapper = loginAttemptMapper; + this.request = request; } @PostMapping("/login") @@ -31,6 +39,16 @@ public class AuthController { String user = body.getOrDefault("username", ""); String pass = body.getOrDefault("password", ""); + var recentQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformLoginAttempt.class) + .eq(PlatformLoginAttempt::getUsername, user) + .eq(PlatformLoginAttempt::getSuccess, false) + .ge(PlatformLoginAttempt::getAttemptedAt, java.time.OffsetDateTime.now().minusMinutes(15)); + long recentFailed = loginAttemptMapper.selectCount(recentQuery); + if (recentFailed >= 5) { + throw new org.springframework.web.server.ResponseStatusException( + org.springframework.http.HttpStatus.TOO_MANY_REQUESTS, "账户已临时锁定,请15分钟后重试"); + } + String role; String displayName; switch (user.toLowerCase()) { @@ -51,10 +69,22 @@ public class AuthController { displayName = "运营账号"; break; default: + PlatformLoginAttempt failedAttempt = new PlatformLoginAttempt(); + failedAttempt.setUsername(user); + failedAttempt.setSuccess(false); + failedAttempt.setIpAddress(request.getRemoteAddr()); + failedAttempt.setAttemptedAt(java.time.OffsetDateTime.now()); + loginAttemptMapper.insert(failedAttempt); throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials"); } if (!pass.equals(user.toLowerCase())) { + PlatformLoginAttempt failedAttempt = new PlatformLoginAttempt(); + failedAttempt.setUsername(user); + failedAttempt.setSuccess(false); + failedAttempt.setIpAddress(request.getRemoteAddr()); + failedAttempt.setAttemptedAt(java.time.OffsetDateTime.now()); + loginAttemptMapper.insert(failedAttempt); throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials"); } @@ -90,6 +120,11 @@ public class AuthController { result.put("roles", List.of(role)); result.put("displayName", displayName); result.put("permissions", permissions); + + var clearQuery = com.baomidou.mybatisplus.core.toolkit.Wrappers.lambdaQuery(PlatformLoginAttempt.class) + .eq(PlatformLoginAttempt::getUsername, user); + loginAttemptMapper.delete(clearQuery); + return result; } diff --git a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/AuditService.java b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/AuditService.java index 93b42a4..7c3c8c8 100644 --- a/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/AuditService.java +++ b/services/delivery-platform-api/src/main/java/cn/craftlabs/platform/api/service/AuditService.java @@ -47,10 +47,19 @@ public class AuditService { auditEventMapper.insert(e); } + @Transactional(readOnly = true) + public List searchAuditEvents( + String entityType, Long entityId, String from, String to, String userId) { + LambdaQueryWrapper q = buildQuery(entityType, entityId, from, to, userId); + return auditEventMapper.selectList(q).stream() + .map(this::toResponse) + .collect(Collectors.toList()); + } + @Transactional(readOnly = true) public PageResponse page( - String entityType, Long entityId, int page, int size) { - LambdaQueryWrapper q = buildQuery(entityType, entityId, null, null); + String entityType, Long entityId, String userId, int page, int size) { + LambdaQueryWrapper q = buildQuery(entityType, entityId, null, null, userId); Page mpPage = new Page<>(page + 1L, size); auditEventMapper.selectPage(mpPage, q); List content = @@ -58,17 +67,8 @@ public class AuditService { return new PageResponse<>(content, mpPage.getTotal(), page, size); } - @Transactional(readOnly = true) - public List searchAuditEvents( - String entityType, Long entityId, String from, String to) { - LambdaQueryWrapper q = buildQuery(entityType, entityId, from, to); - return auditEventMapper.selectList(q).stream() - .map(this::toResponse) - .collect(Collectors.toList()); - } - private LambdaQueryWrapper buildQuery( - String entityType, Long entityId, String from, String to) { + String entityType, Long entityId, String from, String to, String userId) { LambdaQueryWrapper q = Wrappers.lambdaQuery(PlatformAuditEvent.class) .orderByDesc(PlatformAuditEvent::getId); @@ -84,6 +84,9 @@ public class AuditService { if (to != null && !to.isBlank()) { q.le(PlatformAuditEvent::getCreatedAt, OffsetDateTime.parse(to + "T23:59:59Z")); } + if (userId != null && !userId.isBlank()) { + q.eq(PlatformAuditEvent::getActorUserId, userId.trim()); + } return q; }