合并
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
package com.bao.dating.context;
|
||||
|
||||
/**
|
||||
* 用户上下文类,用于保存当前线程的用户ID
|
||||
* 用户上下文类,用于保存当前线程的用户ID和token
|
||||
* @author lenovo
|
||||
*/
|
||||
public class UserContext {
|
||||
|
||||
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
||||
private static final ThreadLocal<String> TOKEN_HOLDER = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* 设置当前线程的用户ID
|
||||
@@ -25,9 +26,26 @@ public class UserContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的用户ID
|
||||
* 设置当前线程的token
|
||||
* @param token 用户token
|
||||
*/
|
||||
public static void setToken(String token) {
|
||||
TOKEN_HOLDER.set(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前线程的token
|
||||
* @return 当前token,如果未设置则返回null
|
||||
*/
|
||||
public static String getToken() {
|
||||
return TOKEN_HOLDER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除当前线程的用户ID和token
|
||||
*/
|
||||
public static void clear() {
|
||||
USER_HOLDER.remove();
|
||||
TOKEN_HOLDER.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,15 @@ import com.bao.dating.anno.Log;
|
||||
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.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.*;
|
||||
@@ -29,15 +33,48 @@ public class UserController {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
private UserDeviceService userDeviceService;
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param userLoginDTO 登录参数
|
||||
* 登录(带设备信息,推荐)
|
||||
* @param loginDTO 登录参数(包含设备信息)
|
||||
* @param request 请求
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO) {
|
||||
UserLoginVO userloginVO = userService.userLogin(userLoginDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userloginVO);
|
||||
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")
|
||||
public Result<UserLoginVO> simpleLogin(@RequestBody UserLoginDTO userLoginDTO) {
|
||||
UserLoginVO userLoginVO = userService.userLogin(userLoginDTO);
|
||||
return Result.success(ResultCode.SUCCESS, "登录成功", userLoginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,4 +252,107 @@ public class UserController {
|
||||
boolean online = userService.isUserOnline(userId);
|
||||
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 请求
|
||||
* @return IP地址
|
||||
*/
|
||||
private String getClientIp(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.getHeader("HTTP_CLIENT_IP");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
|
||||
}
|
||||
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
// 多个代理时,第一个IP为真实IP
|
||||
if (ip != null && ip.contains(",")) {
|
||||
ip = ip.split(",")[0].trim();
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,8 @@ public class TokenInterceptor implements HandlerInterceptor {
|
||||
log.info("用户: {}", userId);
|
||||
// 保存 userId 到 ThreadLocal
|
||||
UserContext.setUserId(userId);
|
||||
// 保存 token 到 ThreadLocal
|
||||
UserContext.setToken(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("Token 校验失败: {}", e.getMessage());
|
||||
|
||||
38
src/main/java/com/bao/dating/mapper/UserDeviceMapper.java
Normal file
38
src/main/java/com/bao/dating/mapper/UserDeviceMapper.java
Normal file
@@ -0,0 +1,38 @@
|
||||
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);
|
||||
}
|
||||
51
src/main/java/com/bao/dating/pojo/dto/UserDeviceDTO.java
Normal file
51
src/main/java/com/bao/dating/pojo/dto/UserDeviceDTO.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.bao.dating.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 用户设备DTO
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserDeviceDTO {
|
||||
|
||||
/**
|
||||
* 设备ID(唯一标识)
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||
*/
|
||||
private Integer deviceType;
|
||||
|
||||
/**
|
||||
* 设备名称(如:iPhone 14 Pro)
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备品牌(如:Apple, Xiaomi)
|
||||
*/
|
||||
private String deviceBrand;
|
||||
|
||||
/**
|
||||
* 操作系统版本
|
||||
*/
|
||||
private String osVersion;
|
||||
|
||||
/**
|
||||
* 浏览器/应用版本
|
||||
*/
|
||||
private String appVersion;
|
||||
|
||||
/**
|
||||
* 设备IP
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 设备位置(根据IP解析,可选)
|
||||
*/
|
||||
private String location;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.bao.dating.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录请求DTO(带设备信息)
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserLoginWithDeviceDTO {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 设备ID(唯一标识)
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||
*/
|
||||
private Integer deviceType;
|
||||
|
||||
/**
|
||||
* 设备名称(如:iPhone 14 Pro)
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备品牌(如:Apple, Xiaomi)
|
||||
*/
|
||||
private String deviceBrand;
|
||||
|
||||
/**
|
||||
* 操作系统版本
|
||||
*/
|
||||
private String osVersion;
|
||||
|
||||
/**
|
||||
* 浏览器/应用版本
|
||||
*/
|
||||
private String appVersion;
|
||||
}
|
||||
78
src/main/java/com/bao/dating/pojo/vo/UserDeviceVO.java
Normal file
78
src/main/java/com/bao/dating/pojo/vo/UserDeviceVO.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.bao.dating.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 用户设备信息VO
|
||||
* @author KilLze
|
||||
*/
|
||||
@Data
|
||||
public class UserDeviceVO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 设备ID
|
||||
*/
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 设备类型:1-Android, 2-iOS, 3-Web, 4-其他
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* 设备名称
|
||||
*/
|
||||
private String deviceName;
|
||||
|
||||
/**
|
||||
* 设备品牌
|
||||
*/
|
||||
private String deviceBrand;
|
||||
|
||||
/**
|
||||
* 操作系统版本
|
||||
*/
|
||||
private String osVersion;
|
||||
|
||||
/**
|
||||
* 应用版本
|
||||
*/
|
||||
private String appVersion;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*/
|
||||
private String ipAddress;
|
||||
|
||||
/**
|
||||
* 设备位置
|
||||
*/
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* 是否当前设备
|
||||
*/
|
||||
private Boolean isCurrent;
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
*/
|
||||
private Boolean isOnline;
|
||||
|
||||
/**
|
||||
* 最后活跃时间
|
||||
*/
|
||||
private LocalDateTime lastActiveAt;
|
||||
|
||||
/**
|
||||
* 登录时间
|
||||
*/
|
||||
private LocalDateTime loginAt;
|
||||
}
|
||||
74
src/main/java/com/bao/dating/service/UserDeviceService.java
Normal file
74
src/main/java/com/bao/dating/service/UserDeviceService.java
Normal file
@@ -0,0 +1,74 @@
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
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 "未知";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user