Compare commits
9 Commits
6b7f6947db
...
feature-cc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8c9e694ba | ||
|
|
c4c8ccff4e | ||
|
|
ab63329f2f | ||
|
|
1028c0773f | ||
|
|
08c6481c51 | ||
|
|
2224b43fcb | ||
|
|
9c1b701594 | ||
|
|
b77952164b | ||
|
|
5210ea9554 |
41
pom.xml
41
pom.xml
@@ -7,41 +7,49 @@
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>dating</name>
|
||||
<description>dating</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<spring-boot.version>2.6.13</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<!-- 核心依赖:Spring Boot基础、数据库访问、Web功能等 -->
|
||||
<dependencies>
|
||||
<!-- Spring Boot 核心启动器 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- MyBatis 持久层框架 -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.5.10</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis 缓存支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Web 开发支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL 数据库连接器 -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok 工具,用于简化实体类开发 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
@@ -49,26 +57,28 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Added MyBatis Spring Boot Starter dependency -->
|
||||
<!-- MyBatis Spring Boot 启动器 -->
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot 测试支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Mockito 测试工具 -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit Platform Launcher for resolving junit-platform-launcher:1.8.2 issue -->
|
||||
<!-- JUnit 5 测试平台启动器 -->
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-launcher</artifactId>
|
||||
@@ -76,71 +86,79 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Commons Lang3 工具包 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHttp(用于调用API) -->
|
||||
<!-- OkHttp HTTP 客户端 -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AOP起步依赖 -->
|
||||
<!-- AOP(面向切面编程)起步依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- WebSocket 起步依赖 -->
|
||||
<!-- WebSocket 实时通信起步依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云相关依赖 -->
|
||||
<!-- 阿里云服务相关依赖 -->
|
||||
<!-- 阿里云对象存储服务(OSS) SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun.oss</groupId>
|
||||
<artifactId>aliyun-sdk-oss</artifactId>
|
||||
<version>3.17.4</version>
|
||||
</dependency>
|
||||
<!-- 阿里云内容安全服务 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>green20220302</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
<!-- FastJSON JSON解析库 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>1.2.83</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 阿里云Java SDK核心库 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||
<version>4.6.3</version>
|
||||
</dependency>
|
||||
<!-- 阿里云绿色服务SDK -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>aliyun-java-sdk-green</artifactId>
|
||||
<version>3.4.2</version>
|
||||
</dependency>
|
||||
<!-- 阿里云图像审核服务 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>imageaudit20191230</artifactId>
|
||||
<version>2.0.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Tea OpenAPI规范实现 -->
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>tea-openapi</artifactId>
|
||||
<version>0.2.8</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT 相关依赖 -->
|
||||
<!-- JWT 认证授权相关依赖 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
@@ -166,18 +184,19 @@
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Mail 邮件发送 -->
|
||||
<!-- Spring Mail 邮件发送功能 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis 依赖 -->
|
||||
<!-- Redis 数据缓存依赖(重复定义,可删除) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- PageHelper 分页插件 -->
|
||||
<dependency>
|
||||
<groupId>com.github.pagehelper</groupId>
|
||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||
@@ -185,8 +204,10 @@
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
<!-- 依赖管理:统一管理项目中使用的依赖版本 -->
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- Spring Boot 依赖管理 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
|
||||
@@ -14,19 +14,21 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
@Bean
|
||||
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||
// 创建RedisTemplate对象
|
||||
RedisTemplate redisTemplate = new RedisTemplate<>();
|
||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
||||
// 设置redis的连接工厂对象
|
||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||
|
||||
// 设置redis key的序列化器
|
||||
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||
// 设置value的序列化器
|
||||
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
// 设置hash类型的key和value的序列化器
|
||||
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
// Key和HashKey都使用String序列化
|
||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
||||
redisTemplate.setKeySerializer(stringSerializer);
|
||||
redisTemplate.setHashKeySerializer(stringSerializer);
|
||||
|
||||
// Value和HashValue使用JSON序列化
|
||||
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
|
||||
redisTemplate.setValueSerializer(jsonSerializer);
|
||||
redisTemplate.setHashValueSerializer(jsonSerializer);
|
||||
|
||||
redisTemplate.afterPropertiesSet();
|
||||
return redisTemplate;
|
||||
|
||||
@@ -6,8 +6,15 @@ package com.bao.dating.context;
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
/**
|
||||
* 当前线程的用户ID
|
||||
*/
|
||||
|
||||
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> TOKEN_HOLDER = new ThreadLocal<>();
|
||||
/**
|
||||
* 当前线程的设备ID
|
||||
*/
|
||||
private static final ThreadLocal<String> DEVICE_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前线程的用户ID
|
||||
@@ -26,19 +33,19 @@ public class UserContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前线程的token
|
||||
* @param token 用户token
|
||||
* 设置当前线程的设备ID
|
||||
* @param deviceId 设备ID
|
||||
*/
|
||||
public static void setToken(String token) {
|
||||
TOKEN_HOLDER.set(token);
|
||||
public static void setDeviceId(String deviceId) {
|
||||
DEVICE_HOLDER.set(deviceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的token
|
||||
* @return 当前token,如果未设置则返回null
|
||||
* 获取当前线程的设备ID
|
||||
* @return 当前设备ID,如果未设置则返回null
|
||||
*/
|
||||
public static String getToken() {
|
||||
return TOKEN_HOLDER.get();
|
||||
public static String getDeviceId() {
|
||||
return DEVICE_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,6 +53,6 @@ public class UserContext {
|
||||
*/
|
||||
public static void clear() {
|
||||
USER_HOLDER.remove();
|
||||
TOKEN_HOLDER.remove();
|
||||
DEVICE_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.anno.Log;
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.context.UserContext;
|
||||
@@ -8,11 +7,9 @@ import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.dto.UserInfoDTO;
|
||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
||||
import com.bao.dating.pojo.dto.UserLoginWithDeviceDTO;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import com.bao.dating.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -34,44 +31,11 @@ public class UserController {
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserDeviceService userDeviceService;
|
||||
|
||||
/**
|
||||
* 登录(带设备信息,推荐)
|
||||
* @param loginDTO 登录参数(包含设备信息)
|
||||
* @param request 请求
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<UserLoginVO> login(@RequestBody UserLoginWithDeviceDTO loginDTO, HttpServletRequest request) {
|
||||
UserLoginDTO userLoginDTO = new UserLoginDTO();
|
||||
userLoginDTO.setUsername(loginDTO.getUsername());
|
||||
userLoginDTO.setPassword(loginDTO.getPassword());
|
||||
|
||||
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||
|
||||
// 记录设备信息
|
||||
if (loginDTO.getDeviceId() != null && !loginDTO.getDeviceId().isEmpty()) {
|
||||
UserDeviceDTO deviceDTO = new UserDeviceDTO();
|
||||
deviceDTO.setDeviceId(loginDTO.getDeviceId());
|
||||
deviceDTO.setDeviceType(loginDTO.getDeviceType());
|
||||
deviceDTO.setDeviceName(loginDTO.getDeviceName());
|
||||
deviceDTO.setDeviceBrand(loginDTO.getDeviceBrand());
|
||||
deviceDTO.setOsVersion(loginDTO.getOsVersion());
|
||||
deviceDTO.setAppVersion(loginDTO.getAppVersion());
|
||||
deviceDTO.setIpAddress(getClientIp(request));
|
||||
|
||||
userDeviceService.recordDevice(userLoginVO.getUserId(), deviceDTO, userLoginVO.getToken());
|
||||
}
|
||||
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单登录(不记录设备信息)
|
||||
* @param userLoginDTO 登录参数
|
||||
*/
|
||||
@PostMapping("/simple-login")
|
||||
@PostMapping("/login")
|
||||
public Result<UserLoginVO> simpleLogin(@RequestBody UserLoginDTO userLoginDTO) {
|
||||
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||
@@ -84,7 +48,8 @@ public class UserController {
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(HttpServletRequest request) {
|
||||
String token = request.getHeader("token");
|
||||
userService.logout(token);
|
||||
String deviceId = request.getHeader("deviceId");
|
||||
userService.logout(token, deviceId);
|
||||
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
|
||||
}
|
||||
|
||||
@@ -104,7 +69,6 @@ public class UserController {
|
||||
* @param file 头像文件
|
||||
* @return 上传后的文件URL列表
|
||||
*/
|
||||
@Log
|
||||
@PostMapping(value = "/info/uploadAvatar", consumes = "multipart/form-data")
|
||||
public Result<String> uploadAvatar(@RequestParam("file") MultipartFile file) {
|
||||
String fileUrl = userService.uploadAvatar(file);
|
||||
@@ -116,7 +80,6 @@ public class UserController {
|
||||
* @param file 背景文件
|
||||
* @return 上传后的文件URL列表
|
||||
*/
|
||||
@Log
|
||||
@PostMapping(value = "/info/uploadBackground", consumes = "multipart/form-data")
|
||||
public Result<String> uploadBackground(@RequestParam("file") MultipartFile file) {
|
||||
String fileUrl = userService.uploadBackground(file);
|
||||
@@ -128,7 +91,6 @@ public class UserController {
|
||||
* @param userInfoUpdateDTO 用户信息更新参数
|
||||
* @return 更新后的用户信息
|
||||
*/
|
||||
@Log
|
||||
@PostMapping("/info/update")
|
||||
public Result<UserInfoVO> userInfoUpdate(@RequestBody UserInfoDTO userInfoUpdateDTO) {
|
||||
Long userId = UserContext.getUserId();
|
||||
@@ -253,80 +215,6 @@ public class UserController {
|
||||
return Result.success(ResultCode.SUCCESS, "查询成功", online);
|
||||
}
|
||||
|
||||
// ==================== 设备管理接口 ====================
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @return 设备列表
|
||||
*/
|
||||
@GetMapping("/devices")
|
||||
public Result<List<UserDeviceVO>> getUserDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/devices/kick/{deviceId}")
|
||||
public Result<Void> kickDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.kickDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设备已下线", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法下线");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
@PostMapping("/devices/kick-others")
|
||||
public Result<Integer> kickOtherDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
// 获取当前设备
|
||||
UserDeviceVO currentDevice = userDeviceService.getDeviceByToken(
|
||||
UserContext.getToken() != null ? UserContext.getToken() : ""
|
||||
);
|
||||
|
||||
String currentDeviceId = currentDevice != null ? currentDevice.getDeviceId() : null;
|
||||
if (currentDeviceId == null) {
|
||||
// 如果找不到当前设备,查询最新的设备
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
if (!devices.isEmpty()) {
|
||||
currentDeviceId = devices.get(0).getDeviceId();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDeviceId == null) {
|
||||
return Result.error(ResultCode.FAIL, "未找到设备");
|
||||
}
|
||||
|
||||
int count = userDeviceService.kickOtherDevices(userId, currentDeviceId);
|
||||
return Result.success(ResultCode.SUCCESS, "已下线 " + count + " 个设备", count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前设备信息
|
||||
* @return 当前设备信息
|
||||
*/
|
||||
@GetMapping("/devices/current")
|
||||
public Result<UserDeviceVO> getCurrentDevice() {
|
||||
String token = UserContext.getToken();
|
||||
if (token == null) {
|
||||
return Result.error(ResultCode.FAIL, "未登录");
|
||||
}
|
||||
UserDeviceVO device = userDeviceService.getDeviceByToken(token);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", device);
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
/**
|
||||
* 获取客户端IP地址
|
||||
* @param request 请求
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package com.bao.dating.controller;
|
||||
|
||||
import com.bao.dating.common.Result;
|
||||
import com.bao.dating.common.ResultCode;
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备管理控制器
|
||||
* @author KilLze
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/user/device")
|
||||
public class UserDeviceController {
|
||||
|
||||
@Autowired
|
||||
private UserDeviceService userDeviceService;
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @return 设备列表
|
||||
*/
|
||||
@GetMapping("/list")
|
||||
public Result<List<UserDeviceVO>> getUserDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
return Result.success(ResultCode.SUCCESS, "获取成功", devices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/kick/{deviceId}")
|
||||
public Result<Void> kickDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.kickDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设备已下线", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法下线");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前设备
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@PostMapping("/current/{deviceId}")
|
||||
public Result<Void> setCurrentDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.setCurrentDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "设置成功", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除设备记录(不能删除当前设备)
|
||||
* @param deviceId 设备ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@DeleteMapping("/{deviceId}")
|
||||
public Result<Void> deleteDevice(@PathVariable String deviceId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
boolean success = userDeviceService.deleteDevice(userId, deviceId);
|
||||
if (success) {
|
||||
return Result.success(ResultCode.SUCCESS, "删除成功", null);
|
||||
} else {
|
||||
return Result.error(ResultCode.FAIL, "设备不存在或无法删除当前设备");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
@PostMapping("/kick-others")
|
||||
public Result<Integer> kickOtherDevices() {
|
||||
Long userId = UserContext.getUserId();
|
||||
// 获取当前设备ID
|
||||
List<UserDeviceVO> devices = userDeviceService.getUserDevices(userId);
|
||||
String currentDeviceId = null;
|
||||
for (UserDeviceVO device : devices) {
|
||||
if (Boolean.TRUE.equals(device.getIsCurrent())) {
|
||||
currentDeviceId = device.getDeviceId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDeviceId == null) {
|
||||
return Result.error(ResultCode.FAIL, "未找到当前设备");
|
||||
}
|
||||
|
||||
int count = userDeviceService.kickOtherDevices(userId, currentDeviceId);
|
||||
return Result.success(ResultCode.SUCCESS, "已下线 " + count + " 个设备", count);
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,15 @@ import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.util.JwtUtil;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* HttpToken拦截器类
|
||||
* 用于拦截请求并验证JWT token的有效性,同时从token中解析用户信息
|
||||
@@ -25,6 +28,7 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
@Autowired
|
||||
private RedisTemplate redisTemplate;
|
||||
|
||||
|
||||
/**
|
||||
* 在请求处理之前进行拦截
|
||||
* 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中
|
||||
@@ -42,28 +46,35 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
//当前拦截到的不是动态方法,直接放行
|
||||
return true;
|
||||
}
|
||||
|
||||
// 从 header 获取 token
|
||||
String token = request.getHeader("token");
|
||||
if (StringUtils.isBlank(token)) {
|
||||
write401(response, "未登录,请先登录");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取 deviceId(多设备关键)
|
||||
String deviceId = request.getHeader("deviceId");
|
||||
if (StringUtils.isBlank(deviceId)) {
|
||||
write401(response, "设备标识缺失");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
log.info("jwt校验: {}", token);
|
||||
log.info("HTTP鉴权 token={}, deviceId={}", token, deviceId);
|
||||
|
||||
// 验证 token 是否有效(包括是否过期)
|
||||
if (!JwtUtil.validateToken(token)) {
|
||||
log.error("Token无效或已过期");
|
||||
response.setStatus(401);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("Token无效或已过期");
|
||||
write401(response, "Token无效或已过期");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查 token 是否在黑名单中
|
||||
Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token);
|
||||
if (blacklistToken != null) {
|
||||
log.error("Token已在黑名单中");
|
||||
response.setStatus(401);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("登录已失效, 请重新登录");
|
||||
write401(response, "登录已失效,请重新登录");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -74,7 +85,6 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
String banKey = "user:ban:" + userId;
|
||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||
log.error("用户 {} 已被封禁,原因:{}", userId, reason);
|
||||
|
||||
response.setStatus(403);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
@@ -82,24 +92,19 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId);
|
||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||
// 多设备 token 校验
|
||||
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||
|
||||
// 验证Redis中的token是否存在且匹配
|
||||
if (redisToken == null || !redisToken.equals(token)) {
|
||||
log.error("登录已失效");
|
||||
response.setStatus(401);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("登录已失效");
|
||||
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
||||
write401(response, "登录状态已失效");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("用户: {}", userId);
|
||||
// 保存 userId 到 ThreadLocal
|
||||
// 保存 登录信息 到 ThreadLocal
|
||||
UserContext.setUserId(userId);
|
||||
// 保存 token 到 ThreadLocal
|
||||
UserContext.setToken(token);
|
||||
UserContext.setDeviceId(deviceId);
|
||||
log.info("token验证成功 userId={}, deviceId={}", userId, deviceId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Token 校验失败: {}", e.getMessage());
|
||||
@@ -122,4 +127,16 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
UserContext.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应错误信息
|
||||
* @param response
|
||||
* @param msg
|
||||
* @throws IOException
|
||||
*/
|
||||
private void write401(HttpServletResponse response, String msg) throws IOException {
|
||||
response.setStatus(401);
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write(msg);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -42,9 +42,10 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
||||
|
||||
// 从URL参数中获取token
|
||||
String token = servletRequest.getParameter("token");
|
||||
String deviceId = servletRequest.getParameter("deviceId");
|
||||
|
||||
if (StringUtils.isBlank(token)) {
|
||||
log.error("WebSocket握手失败:令牌丢失");
|
||||
if (StringUtils.isBlank(token) || StringUtils.isBlank(deviceId)) {
|
||||
log.error("WebSocket认证失败:token或deviceId缺失");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -81,21 +82,19 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 从Redis获取存储的token进行比对
|
||||
String redisTokenKey = "login:token:" + userId;
|
||||
// 多设备 token 校验
|
||||
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||
log.info("Redis中存储的token: {}", redisToken != null ? "存在" : "不存在");
|
||||
|
||||
// 验证Redis中的token是否存在且匹配
|
||||
if (redisToken == null || !redisToken.equals(token)) {
|
||||
log.error("登录已失效 - Redis中token不存在或不匹配");
|
||||
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
||||
log.error("登录已失效");
|
||||
return false;
|
||||
}
|
||||
|
||||
log.info("WebSocket认证成功,用户ID: {}", userId);
|
||||
// 将用户ID保存到attributes中
|
||||
// 将信息保存到attributes中
|
||||
attributes.put("userId", userId);
|
||||
attributes.put("deviceId", deviceId);
|
||||
log.info("WebSocket认证成功 userId={}, deviceId={}", userId, deviceId);
|
||||
return true;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package com.bao.dating.mapper;
|
||||
|
||||
import com.bao.dating.pojo.entity.UserDevice;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备Mapper
|
||||
* @author KilLze
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserDeviceMapper {
|
||||
|
||||
int insert(UserDevice userDevice);
|
||||
|
||||
UserDevice selectByDeviceId(@Param("deviceId") String deviceId);
|
||||
|
||||
List<UserDeviceVO> selectByUserId(@Param("userId") Long userId);
|
||||
|
||||
int updateStatus(@Param("deviceId") String deviceId, @Param("status") Integer status);
|
||||
|
||||
int updateLastActiveAt(@Param("deviceId") String deviceId);
|
||||
|
||||
int clearCurrentDevice(@Param("userId") Long userId);
|
||||
|
||||
int setCurrentDevice(@Param("deviceId") String deviceId);
|
||||
|
||||
int deleteByDeviceId(@Param("deviceId") String deviceId);
|
||||
|
||||
UserDevice selectByToken(@Param("token") String token);
|
||||
|
||||
int update(UserDevice userDevice);
|
||||
|
||||
int deleteByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -12,4 +12,10 @@ import java.io.Serializable;
|
||||
public class UserLoginDTO implements Serializable {
|
||||
private String username;
|
||||
private String password;
|
||||
/** 设备唯一标识(前端生成)*/
|
||||
private String deviceId;
|
||||
/** 设备类型 */
|
||||
private String deviceType;
|
||||
/** 设备名称 */
|
||||
private String deviceName;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ public class UserLoginVO implements Serializable {
|
||||
private Long userId;
|
||||
private String nickname;
|
||||
private String token;
|
||||
private String deviceId;
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
package com.bao.dating.service;
|
||||
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备服务接口
|
||||
* @author KilLze
|
||||
*/
|
||||
public interface UserDeviceService {
|
||||
|
||||
/**
|
||||
* 记录用户登录设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceDTO 设备信息
|
||||
* @param token 登录Token
|
||||
* @return 设备信息
|
||||
*/
|
||||
UserDeviceVO recordDevice(Long userId, UserDeviceDTO deviceDTO, String token);
|
||||
|
||||
/**
|
||||
* 获取用户所有登录设备
|
||||
* @param userId 用户ID
|
||||
* @return 设备列表
|
||||
*/
|
||||
List<UserDeviceVO> getUserDevices(Long userId);
|
||||
|
||||
/**
|
||||
* 强制下线某设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean kickDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 设置某设备为当前设备
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean setCurrentDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 删除设备记录
|
||||
* @param userId 用户ID
|
||||
* @param deviceId 设备ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteDevice(Long userId, String deviceId);
|
||||
|
||||
/**
|
||||
* 更新设备活跃时间
|
||||
* @param token 用户Token
|
||||
*/
|
||||
void updateDeviceActive(String token);
|
||||
|
||||
/**
|
||||
* 根据Token获取设备信息
|
||||
* @param token 用户Token
|
||||
* @return 设备信息
|
||||
*/
|
||||
UserDeviceVO getDeviceByToken(String token);
|
||||
|
||||
/**
|
||||
* 下线其他所有设备(除当前设备外)
|
||||
* @param userId 用户ID
|
||||
* @param currentDeviceId 当前设备ID
|
||||
* @return 被下线的设备数量
|
||||
*/
|
||||
int kickOtherDevices(Long userId, String currentDeviceId);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public interface UserService {
|
||||
* @param token 登录凭证
|
||||
* @return 注册结果
|
||||
*/
|
||||
void logout(String token);
|
||||
void logout(String token, String deviceId);
|
||||
|
||||
/**
|
||||
* 查询个人信息
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
package com.bao.dating.service.impl;
|
||||
|
||||
import com.bao.dating.context.UserContext;
|
||||
import com.bao.dating.mapper.UserDeviceMapper;
|
||||
import com.bao.dating.mapper.UserMapper;
|
||||
import com.bao.dating.pojo.dto.UserDeviceDTO;
|
||||
import com.bao.dating.pojo.entity.User;
|
||||
import com.bao.dating.pojo.entity.UserDevice;
|
||||
import com.bao.dating.pojo.vo.UserDeviceVO;
|
||||
import com.bao.dating.service.UserDeviceService;
|
||||
import com.bao.dating.session.WsSessionManager;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户设备服务实现类
|
||||
* @author KilLze
|
||||
*/
|
||||
@Service
|
||||
public class UserDeviceServiceImpl implements UserDeviceService {
|
||||
|
||||
@Autowired
|
||||
private UserDeviceMapper userDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private UserMapper userMapper;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private WsSessionManager wsSessionManager;
|
||||
|
||||
@Override
|
||||
public UserDeviceVO recordDevice(Long userId, UserDeviceDTO deviceDTO, String token) {
|
||||
// 先查找是否已存在该设备
|
||||
UserDevice existingDevice = userDeviceMapper.selectByDeviceId(deviceDTO.getDeviceId());
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
if (existingDevice != null) {
|
||||
// 设备已存在,更新信息
|
||||
existingDevice.setToken(token);
|
||||
existingDevice.setStatus(1); // 在线
|
||||
existingDevice.setIsCurrent(1); // 设为当前设备
|
||||
existingDevice.setLastActiveAt(now);
|
||||
existingDevice.setIpAddress(deviceDTO.getIpAddress());
|
||||
existingDevice.setLocation(deviceDTO.getLocation());
|
||||
userDeviceMapper.update(existingDevice);
|
||||
|
||||
// 清除该用户其他设备的当前标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
userDeviceMapper.setCurrentDevice(deviceDTO.getDeviceId());
|
||||
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(existingDevice, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(existingDevice.getDeviceType()));
|
||||
vo.setIsCurrent(true);
|
||||
vo.setIsOnline(true);
|
||||
return vo;
|
||||
}
|
||||
|
||||
// 新设备,插入记录
|
||||
UserDevice userDevice = new UserDevice();
|
||||
userDevice.setUserId(userId);
|
||||
userDevice.setDeviceId(deviceDTO.getDeviceId());
|
||||
userDevice.setDeviceType(deviceDTO.getDeviceType());
|
||||
userDevice.setDeviceName(deviceDTO.getDeviceName());
|
||||
userDevice.setDeviceBrand(deviceDTO.getDeviceBrand());
|
||||
userDevice.setOsVersion(deviceDTO.getOsVersion());
|
||||
userDevice.setAppVersion(deviceDTO.getAppVersion());
|
||||
userDevice.setIpAddress(deviceDTO.getIpAddress());
|
||||
userDevice.setLocation(deviceDTO.getLocation());
|
||||
userDevice.setToken(token);
|
||||
userDevice.setStatus(1); // 在线
|
||||
userDevice.setIsCurrent(1); // 设为当前设备
|
||||
userDevice.setLastActiveAt(now);
|
||||
userDevice.setLoginAt(now);
|
||||
|
||||
// 清除该用户其他设备的当前标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
|
||||
userDeviceMapper.insert(userDevice);
|
||||
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(userDevice, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(userDevice.getDeviceType()));
|
||||
vo.setIsCurrent(true);
|
||||
vo.setIsOnline(true);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserDeviceVO> getUserDevices(Long userId) {
|
||||
List<UserDeviceVO> devices = userDeviceMapper.selectByUserId(userId);
|
||||
// 格式化设备类型
|
||||
for (UserDeviceVO device : devices) {
|
||||
device.setDeviceType(getDeviceTypeName(device.getDeviceType()));
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean kickDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
// 验证设备属于该用户
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 删除设备记录
|
||||
userDeviceMapper.deleteByDeviceId(deviceId);
|
||||
|
||||
// 从Redis中删除该设备的登录状态
|
||||
String redisKey = "login:token:" + userId;
|
||||
redisTemplate.delete(redisKey);
|
||||
|
||||
// 如果有WebSocket连接,关闭连接
|
||||
wsSessionManager.removeSession(device.getToken());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setCurrentDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除所有当前设备标记
|
||||
userDeviceMapper.clearCurrentDevice(userId);
|
||||
|
||||
// 设置新的当前设备
|
||||
userDeviceMapper.setCurrentDevice(deviceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteDevice(Long userId, String deviceId) {
|
||||
UserDevice device = userDeviceMapper.selectByDeviceId(deviceId);
|
||||
|
||||
if (device == null || !device.getUserId().equals(userId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 不能删除当前设备
|
||||
if (device.getIsCurrent() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
userDeviceMapper.deleteByDeviceId(deviceId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeviceActive(String token) {
|
||||
if (token != null && !token.isEmpty()) {
|
||||
userDeviceMapper.updateLastActiveAt(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDeviceVO getDeviceByToken(String token) {
|
||||
UserDevice device = userDeviceMapper.selectByToken(token);
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
UserDeviceVO vo = new UserDeviceVO();
|
||||
BeanUtils.copyProperties(device, vo);
|
||||
vo.setDeviceType(getDeviceTypeName(device.getDeviceType()));
|
||||
vo.setIsOnline(device.getStatus() == 1);
|
||||
vo.setIsCurrent(device.getIsCurrent() == 1);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int kickOtherDevices(Long userId, String currentDeviceId) {
|
||||
List<UserDeviceVO> devices = userDeviceMapper.selectByUserId(userId);
|
||||
int count = 0;
|
||||
|
||||
for (UserDeviceVO device : devices) {
|
||||
if (!device.getDeviceId().equals(currentDeviceId)) {
|
||||
// 踢掉设备
|
||||
userDeviceMapper.deleteByDeviceId(device.getDeviceId());
|
||||
|
||||
// 清除登录状态
|
||||
String redisKey = "login:token:" + userId;
|
||||
redisTemplate.delete(redisKey);
|
||||
|
||||
// 关闭WebSocket连接
|
||||
wsSessionManager.removeSession(device.getDeviceId());
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取设备类型名称
|
||||
*/
|
||||
private String getDeviceTypeName(Integer deviceType) {
|
||||
if (deviceType == null) {
|
||||
return "未知";
|
||||
}
|
||||
switch (deviceType) {
|
||||
case 1: return "Android";
|
||||
case 2: return "iOS";
|
||||
case 3: return "Web";
|
||||
case 4: return "其他";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,9 @@ public class UserServiceImpl implements UserService {
|
||||
if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {
|
||||
throw new RuntimeException("用户名或密码不能为空");
|
||||
}
|
||||
if (userLoginDTO.getDeviceId() == null || userLoginDTO.getDeviceName() == null || userLoginDTO.getDeviceType() == null){
|
||||
throw new RuntimeException("未获取到设备");
|
||||
}
|
||||
// 查询用户
|
||||
User user = userMapper.getByUsername(userLoginDTO.getUsername());
|
||||
if (user == null) {
|
||||
@@ -101,19 +104,40 @@ public class UserServiceImpl implements UserService {
|
||||
// 生成token
|
||||
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
||||
|
||||
String redisKey = "login:token:" + user.getUserId();
|
||||
Long userId = user.getUserId();
|
||||
String deviceId = userLoginDTO.getDeviceId();
|
||||
|
||||
// 缓存登录token
|
||||
String tokenKey = "login:token:" + userId+ ":" + deviceId;
|
||||
redisTemplate.opsForValue().set(
|
||||
redisKey,
|
||||
tokenKey,
|
||||
token,
|
||||
7,
|
||||
TimeUnit.DAYS
|
||||
);
|
||||
|
||||
// 设备信息 Hash
|
||||
String deviceKey = "user:device:" + userId+ ":" + deviceId;
|
||||
Map<String, Object> deviceInfo = new HashMap<>();
|
||||
deviceInfo.put("token", token);
|
||||
deviceInfo.put("deviceType", userLoginDTO.getDeviceType());
|
||||
deviceInfo.put("deviceName", userLoginDTO.getDeviceName());
|
||||
deviceInfo.put("loginTime", System.currentTimeMillis());
|
||||
|
||||
// 存储设备信息
|
||||
redisTemplate.opsForHash().putAll(deviceKey, deviceInfo);
|
||||
redisTemplate.expire(deviceKey, 7, TimeUnit.DAYS);
|
||||
|
||||
// 缓存用户设备信息
|
||||
String deviceSetKey = "user:devices:" + userId;
|
||||
redisTemplate.opsForSet().add(deviceSetKey, deviceId);
|
||||
|
||||
// 封装返回
|
||||
UserLoginVO userLoginVO = new UserLoginVO();
|
||||
userLoginVO.setUserId(user.getUserId());
|
||||
userLoginVO.setUserId(userId);
|
||||
userLoginVO.setNickname(user.getNickname());
|
||||
userLoginVO.setToken(token);
|
||||
userLoginVO.setDeviceId(deviceId);
|
||||
return userLoginVO;
|
||||
}
|
||||
|
||||
@@ -122,10 +146,10 @@ public class UserServiceImpl implements UserService {
|
||||
* @param token 登录凭证
|
||||
*/
|
||||
@Override
|
||||
public void logout(String token) {
|
||||
public void logout(String token, String deviceId) {
|
||||
Claims claims = JwtUtil.getClaimsFromToken(token);
|
||||
// 获取token信息
|
||||
String subject = claims.getSubject();
|
||||
String userId = claims.getSubject();
|
||||
// 获取token的过期时间
|
||||
Date expiration = claims.getExpiration();
|
||||
// 判断 token 是否已过期
|
||||
@@ -135,10 +159,15 @@ public class UserServiceImpl implements UserService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 从Redis中删除登录token记录
|
||||
String loginTokenKey = "login:token:" + subject;
|
||||
// 从Redis中删除当前设备登录的token记录
|
||||
String loginTokenKey = "login:token:" + userId + ":" + deviceId;
|
||||
redisTemplate.delete(loginTokenKey);
|
||||
|
||||
// 删除设备信息
|
||||
String deviceKey = "user:device:" + userId + ":" + deviceId;
|
||||
redisTemplate.delete(deviceKey);
|
||||
|
||||
// 将token加入黑名单
|
||||
String logoutKey = "jwt:blacklist:" + token;
|
||||
redisTemplate.opsForValue().set(
|
||||
logoutKey,
|
||||
@@ -499,6 +528,11 @@ public class UserServiceImpl implements UserService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断用户是否在线
|
||||
* @param userId 用户ID
|
||||
* @return true: 在线,false: 离线
|
||||
*/
|
||||
@Override
|
||||
public boolean isUserOnline(Long userId) {
|
||||
if (userId == null) {
|
||||
|
||||
Reference in New Issue
Block a user