我们可以按照以下步骤进行实现:
配置Spring Boot项目:
首先,确保你已经创建了一个Spring Boot项目,并且已经添加了以下依赖:
xml
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Boot Starter JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- MySQL Connector Java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- BCrypt Password Encoder -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
创建SessionContext类用于管理HttpSession对象:
package com.icoderoad.example.demo.util;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
public class SessionContext {
private static final Map<String, HttpSession> sessionMap = new HashMap<>();
public static synchronized void addSession(HttpSession session) {
if (session != null) {
sessionMap.put(session.getId(), session);
}
}
public static synchronized void removeSession(HttpSession session) {
if (session != null) {
sessionMap.remove(session.getId());
}
}
public static synchronized HttpSession getSession(String sessionId) {
return sessionMap.get(sessionId);
}
}
//创建SessionListener类实现HttpSessionListener接口:
package com.icoderoad.example.demo.listener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import com.icoderoad.example.demo.util.SessionContext;
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
SessionContext.addSession(session);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
SessionContext.removeSession(session);
}
}
项目启动类
package com.icoderoad.example.demo;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import com.icoderoad.example.demo.listener.SessionListener;
@SpringBootApplication
@MapperScan("com.icoderoad.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public ServletListenerRegistrationBean<SessionListener> sessionListener() {
ServletListenerRegistrationBean<SessionListener> listenerBean =
new ServletListenerRegistrationBean<>();
listenerBean.setListener(new SessionListener());
return listenerBean;
}
}
用户实体类
java
package com.icoderoad.example.demo.entity;
import java.time.LocalDateTime;
import lombok.Data;
@Data
public class User {
private Long id;
private String userName;
private String name;
private String phone;
private String email;
private LocalDateTime createTime;
}
创建用户服务层:
编写用户服务层UserService,处理用户登录、保存用户信息到Session等逻辑。
java
package com.icoderoad.example.demo.service;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.icoderoad.example.demo.entity.User;
import com.icoderoad.example.demo.mapper.UserMapper;
import com.icoderoad.example.demo.util.SessionContext;
@Service
public class UserService {
private final UserMapper userMapper;
private final RedisTemplate<String, String> redisTemplate;
private final BCryptPasswordEncoder passwordEncoder;
@Autowired
public UserService(UserMapper userMapper, RedisTemplate<String, String> redisTemplate) {
this.userMapper = userMapper;
this.redisTemplate = redisTemplate;
this.passwordEncoder = new BCryptPasswordEncoder();
}
public boolean login(String userName, String password, HttpSession session) {
// 验证用户名和密码是否匹配
boolean loginSuccess = this.validateUser(userName, password);
// 在登录成功后,判断是否已经有用户登录了
if( loginSuccess ){
String previousSessionId = redisTemplate.opsForValue().get(userName);
if (previousSessionId != null) {
// 如果存在已登录用户,使其Session失效,实现踢出功能
HttpSession previousSession = SessionContext.getSession(previousSessionId);
if (previousSession != null) {
previousSession.invalidate();
}
}
User user = getUserByUsername(userName);
session.setAttribute("loggedInUser", user);
// 更新Redis中的用户Session信息
redisTemplate.opsForValue().set(userName, session.getId());
}
return true;
}
public boolean validateUser(String userName, String password) {
// 根据用户名查询用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", userName);
User user = userMapper.selectOne(queryWrapper);
// 判断用户是否存在并且密码是否匹配
if (user != null && passwordEncoder.matches(password, user.getPassword())) {
return true; // 用户名和密码匹配
}
return false; // 用户名或密码错误
}
public User getUserByUsername(String userName) {
// 根据用户名查询用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", userName);
return userMapper.selectOne(queryWrapper);
}
public void registerUser(User user) {
// 对密码进行BCrypt哈希加密
String hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
// 插入用户信息到数据库
userMapper.insert(user);
}
}
控制器层:
创建控制器层UserController,处理用户登录请求。
java
package com.icoderoad.example.demo.controller;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.icoderoad.example.demo.entity.User;
import com.icoderoad.example.demo.service.UserService;
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/login")
public String login(@RequestParam String userName,
@RequestParam String password,
HttpServletRequest request) {
HttpSession session = request.getSession(true);
// 验证登录并处理踢出逻辑
boolean loginSuccess = userService.login(userName, password, session);
if (loginSuccess) {
// 登录成功,根据业务需求跳转到相应页面
return "登陆成功";
} else {
// 登录失败,返回登录页或错误提示页
return "登陆失败";
}
}
@PostMapping("/register")
public String register(@RequestBody User user) {
// 注册用户
userService.registerUser(user);
return "注册成功";
}
@GetMapping("validate")
public String validate(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if( session!=null ) {
User user = !Objects.isNull(session.getAttribute("loggedInUser")) ? (User)session.getAttribute("loggedInUser"): null;
if( user!=null ) {
return "用户:" + user.getUserName() + "已登陆";
}
}
return "用户退出";
}
}
在上述代码中,我们使用Redis来存储已登录用户的Session信息,并通过Session ID来实现踢出功能。如果用户登录成功,并且已经有相同用户名的用户登录了,我们会使前一个用户的Session失效,然后更新Redis中的Session信息为当前用户的Session ID。