fix: add userId filter to audit search and login lockout logic

This commit is contained in:
2026-05-25 14:46:42 +08:00
parent e96383433d
commit 85d2b85b6a
3 changed files with 55 additions and 15 deletions
@@ -34,19 +34,21 @@ public class AuditController {
public PageResponse<AuditEventResponse> 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<Resource> 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<AuditEventResponse> events = auditService.searchAuditEvents(entityType, entityId, from, to);
List<AuditEventResponse> events = auditService.searchAuditEvents(entityType, entityId, from, to, userId);
StringBuilder sb = new StringBuilder();
sb.append("时间,操作者,动作,实体类型,实体ID,摘要,详情\n");
@@ -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;
}
@@ -47,10 +47,19 @@ public class AuditService {
auditEventMapper.insert(e);
}
@Transactional(readOnly = true)
public List<AuditEventResponse> searchAuditEvents(
String entityType, Long entityId, String from, String to, String userId) {
LambdaQueryWrapper<PlatformAuditEvent> q = buildQuery(entityType, entityId, from, to, userId);
return auditEventMapper.selectList(q).stream()
.map(this::toResponse)
.collect(Collectors.toList());
}
@Transactional(readOnly = true)
public PageResponse<AuditEventResponse> page(
String entityType, Long entityId, int page, int size) {
LambdaQueryWrapper<PlatformAuditEvent> q = buildQuery(entityType, entityId, null, null);
String entityType, Long entityId, String userId, int page, int size) {
LambdaQueryWrapper<PlatformAuditEvent> q = buildQuery(entityType, entityId, null, null, userId);
Page<PlatformAuditEvent> mpPage = new Page<>(page + 1L, size);
auditEventMapper.selectPage(mpPage, q);
List<AuditEventResponse> content =
@@ -58,17 +67,8 @@ public class AuditService {
return new PageResponse<>(content, mpPage.getTotal(), page, size);
}
@Transactional(readOnly = true)
public List<AuditEventResponse> searchAuditEvents(
String entityType, Long entityId, String from, String to) {
LambdaQueryWrapper<PlatformAuditEvent> q = buildQuery(entityType, entityId, from, to);
return auditEventMapper.selectList(q).stream()
.map(this::toResponse)
.collect(Collectors.toList());
}
private LambdaQueryWrapper<PlatformAuditEvent> buildQuery(
String entityType, Long entityId, String from, String to) {
String entityType, Long entityId, String from, String to, String userId) {
LambdaQueryWrapper<PlatformAuditEvent> 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;
}