本文目录导读:

实现登录日志是Java应用中的常见需求,主要有几种典型的实现方式,下面从基础实现到生产级方案进行说明。
基础实现:使用AOP + 数据库
这是最常用且标准的方案,通过Spring AOP拦截登录操作,记录日志到数据库。
1 数据库表设计
CREATE TABLE sys_login_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
ip VARCHAR(50) COMMENT '登录IP',
location VARCHAR(100) COMMENT '登录地点',
browser VARCHAR(100) COMMENT '浏览器',
os VARCHAR(100) COMMENT '操作系统',
status TINYINT NOT NULL COMMENT '状态:1成功 0失败',
message VARCHAR(255) COMMENT '提示消息',
login_time DATETIME NOT NULL COMMENT '登录时间',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2 实体类
@Data
@TableName("sys_login_log")
public class LoginLog {
private Long id;
private String username;
private String ip;
private String location;
private String browser;
private String os;
private Integer status;
private String message;
private LocalDateTime loginTime;
private LocalDateTime createdTime;
}
3 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginLog {
String value() default "";
}
4 AOP切面实现
@Aspect
@Component
@Slf4j
public class LoginLogAspect {
@Autowired
private LoginLogService loginLogService;
@Around("@annotation(loginLogAnnotation)")
public Object around(ProceedingJoinPoint point, LoginLog loginLogAnnotation) throws Throwable {
// 获取请求上下文
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
// 构建日志对象
LoginLog loginLog = new LoginLog();
loginLog.setLoginTime(LocalDateTime.now());
loginLog.setIp(getIpAddress(request));
loginLog.setBrowser(getBrowser(request));
loginLog.setOs(getOperatingSystem(request));
// 获取用户名(从请求参数中获取)
Object[] args = point.getArgs();
if (args.length > 0) {
loginLog.setUsername(String.valueOf(args[0]));
}
try {
Object result = point.proceed();
// 登录成功
loginLog.setStatus(1);
loginLog.setMessage("登录成功");
return result;
} catch (Exception e) {
// 登录失败
loginLog.setStatus(0);
loginLog.setMessage(e.getMessage());
throw e;
} finally {
// 异步保存日志
loginLogService.save(loginLog);
}
}
/**
* 获取客户端IP
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 获取浏览器信息
*/
private String getBrowser(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
// 使用UserAgentUtils解析(需要引入依赖)
return "Chrome 120.0"; // 简化实现
}
/**
* 获取操作系统
*/
private String getOperatingSystem(HttpServletRequest request) {
String userAgent = request.getHeader("User-Agent");
return "Windows 10"; // 简化实现
}
}
5 登录Controller使用
@RestController
public class LoginController {
@PostMapping("/login")
@LoginLog // 使用自定义注解
public Result login(@RequestParam String username, @RequestParam String password) {
// 登录逻辑
if ("admin".equals(username) && "123456".equals(password)) {
return Result.success("登录成功");
} else {
throw new BusinessException("用户名或密码错误");
}
}
}
使用Spring Security + 事件监听
如果项目使用Spring Security,可以通过监听AuthenticationSuccessEvent和AuthenticationFailureBadCredentialsEvent事件来实现。
1 事件监听器
@Component
@Slf4j
public class AuthenticationEventListener {
@Autowired
private LoginLogService loginLogService;
@EventListener
public void onSuccess(AuthenticationSuccessEvent event) {
UserDetails userDetails = (UserDetails) event.getAuthentication().getPrincipal();
LoginLog loginLog = buildLoginLog(userDetails.getUsername(), 1, "登录成功");
loginLogService.save(loginLog);
}
@EventListener
public void onFailure(AuthenticationFailureBadCredentialsEvent event) {
String username = (String) event.getAuthentication().getPrincipal();
LoginLog loginLog = buildLoginLog(username, 0, "用户名或密码错误");
loginLogService.save(loginLog);
}
private LoginLog buildLoginLog(String username, Integer status, String message) {
LoginLog loginLog = new LoginLog();
loginLog.setUsername(username);
loginLog.setStatus(status);
loginLog.setMessage(message);
loginLog.setLoginTime(LocalDateTime.now());
// 获取IP等信息
return loginLog;
}
}
高级功能:登录地点解析
使用IP库解析登录地点(需要引入IP库):
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>2.7.0</version>
</dependency>
@Component
public class IpLocationUtils {
private static DbSearcher searcher;
static {
try {
String dbPath = IpLocationUtils.class.getResource("/ip2region.xdb").getPath();
searcher = new DbSearcher(new DbConfig(), dbPath);
} catch (Exception e) {
log.error("IP库加载失败", e);
}
}
public static String getLocation(String ip) {
try {
Searcher searcher = Searcher.newWithFileOnly("ip2region.xdb");
String region = searcher.search(ip);
// region格式:中国|0|上海|上海市|电信
String[] parts = region.split("\\|");
return parts[0] + parts[2] + parts[3];
} catch (Exception e) {
return "未知";
}
}
}
性能优化:异步日志写入
避免日志写入影响登录性能,使用异步方式:
1 Spring异步支持
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
2 异步Service
@Service
@Slf4j
public class LoginLogService {
@Autowired
private LoginLogMapper loginLogMapper;
@Async // 异步执行
public void save(LoginLog loginLog) {
try {
loginLogMapper.insert(loginLog);
} catch (Exception e) {
log.error("保存登录日志失败", e);
}
}
}
消息队列方案(高并发场景)
对于高并发系统,使用MQ异步处理:
@Service
public class LoginLogService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void save(LoginLog loginLog) {
// 发送到消息队列
rabbitTemplate.convertAndSend("login.log.exchange", "login.log", loginLog);
}
}
@Component
@RabbitListener(queues = "login.log.queue")
public class LoginLogConsumer {
@Autowired
private LoginLogMapper loginLogMapper;
@RabbitHandler
public void process(LoginLog loginLog) {
try {
loginLogMapper.insert(loginLog);
} catch (Exception e) {
log.error("消费登录日志消息失败", e);
// 可以重试或发送到死信队列
}
}
}
查询接口示例
@RestController
@RequestMapping("/api/logs")
public class LoginLogController {
@Autowired
private LoginLogService loginLogService;
@GetMapping("/login")
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String username,
@RequestParam(required = false) Integer status,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
Page<LoginLog> pageInfo = loginLogService.queryPage(page, size, username, status, startTime, endTime);
return Result.success(pageInfo);
}
@DeleteMapping("/login/{id}")
public Result delete(@PathVariable Long id) {
loginLogService.removeById(id);
return Result.success();
}
}
推荐方案
- 中小型项目:AOP + 数据库 + @Async异步写入
- Spring Security项目:事件监听 + 异步写入
- 高并发项目:AOP + 消息队列 + 批量写入
完整的登录日志系统还应该包括:
- 日志清理策略(定期删除旧日志)
- 异常登录告警(如连续失败N次)
- 日志统计分析(登录趋势、活跃度等)
需要根据实际项目规模选择合适的实现方案。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播