Compare commits
1 Commits
feature-cc
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e434e401a |
41
pom.xml
41
pom.xml
@@ -7,49 +7,41 @@
|
|||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<name>dating</name>
|
<name>dating</name>
|
||||||
<description>dating</description>
|
<description>dating</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
<spring-boot.version>2.6.13</spring-boot.version>
|
<spring-boot.version>2.6.13</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<!-- 核心依赖:Spring Boot基础、数据库访问、Web功能等 -->
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Spring Boot 核心启动器 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter</artifactId>
|
<artifactId>spring-boot-starter</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MyBatis 持久层框架 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis</groupId>
|
<groupId>org.mybatis</groupId>
|
||||||
<artifactId>mybatis</artifactId>
|
<artifactId>mybatis</artifactId>
|
||||||
<version>3.5.10</version>
|
<version>3.5.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Redis 缓存支持 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot Web 开发支持 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MySQL 数据库连接器 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Lombok 工具,用于简化实体类开发 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
@@ -57,28 +49,26 @@
|
|||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- MyBatis Spring Boot 启动器 -->
|
<!-- Added MyBatis Spring Boot Starter dependency -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mybatis.spring.boot</groupId>
|
<groupId>org.mybatis.spring.boot</groupId>
|
||||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||||
<version>2.2.0</version>
|
<version>2.2.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Boot 测试支持 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Mockito 测试工具 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-inline</artifactId>
|
<artifactId>mockito-inline</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JUnit 5 测试平台启动器 -->
|
<!-- JUnit Platform Launcher for resolving junit-platform-launcher:1.8.2 issue -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.platform</groupId>
|
<groupId>org.junit.platform</groupId>
|
||||||
<artifactId>junit-platform-launcher</artifactId>
|
<artifactId>junit-platform-launcher</artifactId>
|
||||||
@@ -86,79 +76,71 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Apache Commons Lang3 工具包 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.12.0</version>
|
<version>3.12.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- OkHttp HTTP 客户端 -->
|
<!-- OkHttp(用于调用API) -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
<version>4.12.0</version>
|
<version>4.12.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- AOP(面向切面编程)起步依赖 -->
|
<!-- AOP起步依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-aop</artifactId>
|
<artifactId>spring-boot-starter-aop</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- WebSocket 实时通信起步依赖 -->
|
<!-- WebSocket 起步依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 阿里云服务相关依赖 -->
|
<!-- 阿里云相关依赖 -->
|
||||||
<!-- 阿里云对象存储服务(OSS) SDK -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun.oss</groupId>
|
<groupId>com.aliyun.oss</groupId>
|
||||||
<artifactId>aliyun-sdk-oss</artifactId>
|
<artifactId>aliyun-sdk-oss</artifactId>
|
||||||
<version>3.17.4</version>
|
<version>3.17.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 阿里云内容安全服务 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>green20220302</artifactId>
|
<artifactId>green20220302</artifactId>
|
||||||
<version>3.0.1</version>
|
<version>3.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- FastJSON JSON解析库 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.alibaba</groupId>
|
<groupId>com.alibaba</groupId>
|
||||||
<artifactId>fastjson</artifactId>
|
<artifactId>fastjson</artifactId>
|
||||||
<version>1.2.83</version>
|
<version>1.2.83</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 阿里云Java SDK核心库 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-core</artifactId>
|
<artifactId>aliyun-java-sdk-core</artifactId>
|
||||||
<version>4.6.3</version>
|
<version>4.6.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 阿里云绿色服务SDK -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>aliyun-java-sdk-green</artifactId>
|
<artifactId>aliyun-java-sdk-green</artifactId>
|
||||||
<version>3.4.2</version>
|
<version>3.4.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- 阿里云图像审核服务 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>imageaudit20191230</artifactId>
|
<artifactId>imageaudit20191230</artifactId>
|
||||||
<version>2.0.6</version>
|
<version>2.0.6</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Tea OpenAPI规范实现 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.aliyun</groupId>
|
<groupId>com.aliyun</groupId>
|
||||||
<artifactId>tea-openapi</artifactId>
|
<artifactId>tea-openapi</artifactId>
|
||||||
<version>0.2.8</version>
|
<version>0.2.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- JWT 认证授权相关依赖 -->
|
<!-- JWT 相关依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>jjwt-api</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
@@ -184,19 +166,18 @@
|
|||||||
<version>3.0.0</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Mail 邮件发送功能 -->
|
<!-- Spring Mail 邮件发送 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-mail</artifactId>
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Redis 数据缓存依赖(重复定义,可删除) -->
|
<!-- Redis 依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- PageHelper 分页插件 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.pagehelper</groupId>
|
<groupId>com.github.pagehelper</groupId>
|
||||||
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
<artifactId>pagehelper-spring-boot-starter</artifactId>
|
||||||
@@ -204,10 +185,8 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<!-- 依赖管理:统一管理项目中使用的依赖版本 -->
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- Spring Boot 依赖管理 -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
|||||||
@@ -14,21 +14,19 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
|
|||||||
@Configuration
|
@Configuration
|
||||||
public class RedisConfig {
|
public class RedisConfig {
|
||||||
@Bean
|
@Bean
|
||||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
|
||||||
// 创建RedisTemplate对象
|
// 创建RedisTemplate对象
|
||||||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
|
RedisTemplate redisTemplate = new RedisTemplate<>();
|
||||||
// 设置redis的连接工厂对象
|
// 设置redis的连接工厂对象
|
||||||
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
redisTemplate.setConnectionFactory(redisConnectionFactory);
|
||||||
|
|
||||||
// Key和HashKey都使用String序列化
|
// 设置redis key的序列化器
|
||||||
StringRedisSerializer stringSerializer = new StringRedisSerializer();
|
redisTemplate.setKeySerializer(new StringRedisSerializer());
|
||||||
redisTemplate.setKeySerializer(stringSerializer);
|
// 设置value的序列化器
|
||||||
redisTemplate.setHashKeySerializer(stringSerializer);
|
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||||
|
// 设置hash类型的key和value的序列化器
|
||||||
// Value和HashValue使用JSON序列化
|
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
|
||||||
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
|
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||||
redisTemplate.setValueSerializer(jsonSerializer);
|
|
||||||
redisTemplate.setHashValueSerializer(jsonSerializer);
|
|
||||||
|
|
||||||
redisTemplate.afterPropertiesSet();
|
redisTemplate.afterPropertiesSet();
|
||||||
return redisTemplate;
|
return redisTemplate;
|
||||||
|
|||||||
@@ -6,15 +6,9 @@ package com.bao.dating.context;
|
|||||||
*/
|
*/
|
||||||
public class UserContext {
|
public class UserContext {
|
||||||
|
|
||||||
/**
|
|
||||||
* 当前线程的用户ID
|
|
||||||
*/
|
|
||||||
|
|
||||||
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
private static final ThreadLocal<Long> USER_HOLDER = new ThreadLocal<>();
|
||||||
/**
|
|
||||||
* 当前线程的设备ID
|
private static final ThreadLocal<String> TOKEN_HOLDER = new ThreadLocal<>();
|
||||||
*/
|
|
||||||
private static final ThreadLocal<String> DEVICE_HOLDER = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置当前线程的用户ID
|
* 设置当前线程的用户ID
|
||||||
@@ -33,19 +27,19 @@ public class UserContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置当前线程的设备ID
|
* 设置当前线程的token
|
||||||
* @param deviceId 设备ID
|
* @param token 要设置的token字符串
|
||||||
*/
|
*/
|
||||||
public static void setDeviceId(String deviceId) {
|
public static void setToken(String token) {
|
||||||
DEVICE_HOLDER.set(deviceId);
|
TOKEN_HOLDER.set(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前线程的设备ID
|
* 获取当前线程的token
|
||||||
* @return 当前设备ID,如果未设置则返回null
|
* @return 当前token,如果未设置则返回null
|
||||||
*/
|
*/
|
||||||
public static String getDeviceId() {
|
public static String getToken() {
|
||||||
return DEVICE_HOLDER.get();
|
return TOKEN_HOLDER.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +47,6 @@ public class UserContext {
|
|||||||
*/
|
*/
|
||||||
public static void clear() {
|
public static void clear() {
|
||||||
USER_HOLDER.remove();
|
USER_HOLDER.remove();
|
||||||
DEVICE_HOLDER.remove();
|
TOKEN_HOLDER.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.bao.dating.pojo.dto.UserDeviceDTO;
|
|||||||
import com.bao.dating.pojo.dto.UserInfoDTO;
|
import com.bao.dating.pojo.dto.UserInfoDTO;
|
||||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
import com.bao.dating.pojo.dto.UserLoginDTO;
|
||||||
import com.bao.dating.pojo.dto.UserLoginWithDeviceDTO;
|
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.UserDeviceVO;
|
||||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||||
@@ -48,8 +49,7 @@ public class UserController {
|
|||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public Result<Void> logout(HttpServletRequest request) {
|
public Result<Void> logout(HttpServletRequest request) {
|
||||||
String token = request.getHeader("token");
|
String token = request.getHeader("token");
|
||||||
String deviceId = request.getHeader("deviceId");
|
userService.logout(token);
|
||||||
userService.logout(token, deviceId);
|
|
||||||
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
|
return Result.success(ResultCode.SUCCESS,"退出登录成功",null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,4 +243,19 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户名/爱好标签搜索用户
|
||||||
|
*
|
||||||
|
* @param userName 用户名(模糊匹配)
|
||||||
|
* @param hobbies 爱好标签(精确匹配单个标签)
|
||||||
|
* @return 符合条件的用户列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/api/users/search")
|
||||||
|
public List<User> searchUsers(
|
||||||
|
@RequestParam(required = false) String userName,
|
||||||
|
@RequestParam(required = false) String hobbies) {
|
||||||
|
// 调用Service层的搜索方法
|
||||||
|
return userService.searchUsers(userName, hobbies);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,15 +7,12 @@ import com.bao.dating.context.UserContext;
|
|||||||
import com.bao.dating.util.JwtUtil;
|
import com.bao.dating.util.JwtUtil;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.servlet.HandlerInterceptor;
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HttpToken拦截器类
|
* HttpToken拦截器类
|
||||||
* 用于拦截请求并验证JWT token的有效性,同时从token中解析用户信息
|
* 用于拦截请求并验证JWT token的有效性,同时从token中解析用户信息
|
||||||
@@ -28,7 +25,6 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate redisTemplate;
|
private RedisTemplate redisTemplate;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在请求处理之前进行拦截
|
* 在请求处理之前进行拦截
|
||||||
* 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中
|
* 从请求头或URL参数中获取token,验证其有效性,并将用户ID保存到ThreadLocal中
|
||||||
@@ -46,35 +42,28 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
//当前拦截到的不是动态方法,直接放行
|
//当前拦截到的不是动态方法,直接放行
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 header 获取 token
|
// 从 header 获取 token
|
||||||
String token = request.getHeader("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 {
|
try {
|
||||||
log.info("HTTP鉴权 token={}, deviceId={}", token, deviceId);
|
log.info("jwt校验: {}", token);
|
||||||
|
|
||||||
// 验证 token 是否有效(包括是否过期)
|
// 验证 token 是否有效(包括是否过期)
|
||||||
if (!JwtUtil.validateToken(token)) {
|
if (!JwtUtil.validateToken(token)) {
|
||||||
write401(response, "Token无效或已过期");
|
log.error("Token无效或已过期");
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("Token无效或已过期");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 token 是否在黑名单中
|
// 检查 token 是否在黑名单中
|
||||||
Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token);
|
Object blacklistToken = redisTemplate.opsForValue().get("jwt:blacklist:" + token);
|
||||||
if (blacklistToken != null) {
|
if (blacklistToken != null) {
|
||||||
write401(response, "登录已失效,请重新登录");
|
log.error("Token已在黑名单中");
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("登录已失效, 请重新登录");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +74,7 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
String banKey = "user:ban:" + userId;
|
String banKey = "user:ban:" + userId;
|
||||||
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
if (Boolean.TRUE.equals(redisTemplate.hasKey(banKey))) {
|
||||||
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
String reason = String.valueOf(redisTemplate.opsForValue().get(banKey));
|
||||||
|
log.error("用户 {} 已被封禁,原因:{}", userId, reason);
|
||||||
|
|
||||||
response.setStatus(403);
|
response.setStatus(403);
|
||||||
response.setContentType("application/json;charset=UTF-8");
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
@@ -92,19 +82,24 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多设备 token 校验
|
// 从Redis获取存储的token进行比对
|
||||||
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
Object redisTokenObj = redisTemplate.opsForValue().get("login:token:" + userId);
|
||||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||||
|
|
||||||
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
// 验证Redis中的token是否存在且匹配
|
||||||
write401(response, "登录状态已失效");
|
if (redisToken == null || !redisToken.equals(token)) {
|
||||||
|
log.error("登录已失效");
|
||||||
|
response.setStatus(401);
|
||||||
|
response.setContentType("application/json;charset=UTF-8");
|
||||||
|
response.getWriter().write("登录已失效");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存 登录信息 到 ThreadLocal
|
log.info("用户: {}", userId);
|
||||||
|
// 保存 userId 到 ThreadLocal
|
||||||
UserContext.setUserId(userId);
|
UserContext.setUserId(userId);
|
||||||
UserContext.setDeviceId(deviceId);
|
// 保存 token 到 ThreadLocal
|
||||||
log.info("token验证成功 userId={}, deviceId={}", userId, deviceId);
|
UserContext.setToken(token);
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Token 校验失败: {}", e.getMessage());
|
log.error("Token 校验失败: {}", e.getMessage());
|
||||||
@@ -127,16 +122,4 @@ public class TokenInterceptor implements HandlerInterceptor {
|
|||||||
UserContext.clear();
|
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,10 +42,9 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
|||||||
|
|
||||||
// 从URL参数中获取token
|
// 从URL参数中获取token
|
||||||
String token = servletRequest.getParameter("token");
|
String token = servletRequest.getParameter("token");
|
||||||
String deviceId = servletRequest.getParameter("deviceId");
|
|
||||||
|
if (StringUtils.isBlank(token)) {
|
||||||
if (StringUtils.isBlank(token) || StringUtils.isBlank(deviceId)) {
|
log.error("WebSocket握手失败:令牌丢失");
|
||||||
log.error("WebSocket认证失败:token或deviceId缺失");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,19 +81,21 @@ public class WsAuthInterceptor implements HandshakeInterceptor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多设备 token 校验
|
// 从Redis获取存储的token进行比对
|
||||||
String redisTokenKey = "login:token:" + userId + ":" + deviceId;
|
String redisTokenKey = "login:token:" + userId;
|
||||||
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
Object redisTokenObj = redisTemplate.opsForValue().get(redisTokenKey);
|
||||||
|
String redisToken = redisTokenObj != null ? redisTokenObj.toString() : null;
|
||||||
|
log.info("Redis中存储的token: {}", redisToken != null ? "存在" : "不存在");
|
||||||
|
|
||||||
if (redisTokenObj == null || !token.equals(redisTokenObj.toString())) {
|
// 验证Redis中的token是否存在且匹配
|
||||||
log.error("登录已失效");
|
if (redisToken == null || !redisToken.equals(token)) {
|
||||||
|
log.error("登录已失效 - Redis中token不存在或不匹配");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将信息保存到attributes中
|
log.info("WebSocket认证成功,用户ID: {}", userId);
|
||||||
|
// 将用户ID保存到attributes中
|
||||||
attributes.put("userId", userId);
|
attributes.put("userId", userId);
|
||||||
attributes.put("deviceId", deviceId);
|
|
||||||
log.info("WebSocket认证成功 userId={}, deviceId={}", userId, deviceId);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
|
|||||||
@@ -68,4 +68,13 @@ public interface UserMapper {
|
|||||||
* @return 用户列表
|
* @return 用户列表
|
||||||
*/
|
*/
|
||||||
List<UserInfoVO> findByLatLngRange(@Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLng") double minLng, @Param("maxLng") double maxLng);
|
List<UserInfoVO> findByLatLngRange(@Param("minLat") double minLat, @Param("maxLat") double maxLat, @Param("minLng") double minLng, @Param("maxLng") double maxLng);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索用户
|
||||||
|
* @param userName 用户名
|
||||||
|
* @param hobbyTag 爱好标签
|
||||||
|
* @return 用户列表
|
||||||
|
*/
|
||||||
|
List<User> searchUsers(@Param("userName") String userName, @Param("hobbies") String hobbyTag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,4 @@ import java.io.Serializable;
|
|||||||
public class UserLoginDTO implements Serializable {
|
public class UserLoginDTO implements Serializable {
|
||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
/** 设备唯一标识(前端生成)*/
|
|
||||||
private String deviceId;
|
|
||||||
/** 设备类型 */
|
|
||||||
private String deviceType;
|
|
||||||
/** 设备名称 */
|
|
||||||
private String deviceName;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,4 @@ public class UserLoginVO implements Serializable {
|
|||||||
private Long userId;
|
private Long userId;
|
||||||
private String nickname;
|
private String nickname;
|
||||||
private String token;
|
private String token;
|
||||||
private String deviceId;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.bao.dating.service;
|
|||||||
|
|
||||||
import com.bao.dating.pojo.dto.UserInfoDTO;
|
import com.bao.dating.pojo.dto.UserInfoDTO;
|
||||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
import com.bao.dating.pojo.dto.UserLoginDTO;
|
||||||
|
import com.bao.dating.pojo.entity.User;
|
||||||
import com.bao.dating.pojo.vo.UserInfoVO;
|
import com.bao.dating.pojo.vo.UserInfoVO;
|
||||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -25,7 +26,7 @@ public interface UserService {
|
|||||||
* @param token 登录凭证
|
* @param token 登录凭证
|
||||||
* @return 注册结果
|
* @return 注册结果
|
||||||
*/
|
*/
|
||||||
void logout(String token, String deviceId);
|
void logout(String token);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询个人信息
|
* 查询个人信息
|
||||||
@@ -99,4 +100,12 @@ public interface UserService {
|
|||||||
* @return 是否在线
|
* @return 是否在线
|
||||||
*/
|
*/
|
||||||
boolean isUserOnline(Long userId);
|
boolean isUserOnline(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索用户
|
||||||
|
* @param userName 用户名(模糊)
|
||||||
|
* @param hobbies 爱好标签(精确)
|
||||||
|
* @return 用户列表
|
||||||
|
*/
|
||||||
|
List<User> searchUsers(String userName, String hobbies);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
public class UserServiceImpl implements UserService {
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SmsUtil smsUtil;
|
private SmsUtil smsUtil;
|
||||||
|
|
||||||
@@ -82,9 +83,6 @@ public class UserServiceImpl implements UserService {
|
|||||||
if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {
|
if (userLoginDTO == null || userLoginDTO.getUsername() == null || userLoginDTO.getPassword() == null) {
|
||||||
throw new RuntimeException("用户名或密码不能为空");
|
throw new RuntimeException("用户名或密码不能为空");
|
||||||
}
|
}
|
||||||
if (userLoginDTO.getDeviceId() == null || userLoginDTO.getDeviceName() == null || userLoginDTO.getDeviceType() == null){
|
|
||||||
throw new RuntimeException("未获取到设备");
|
|
||||||
}
|
|
||||||
// 查询用户
|
// 查询用户
|
||||||
User user = userMapper.getByUsername(userLoginDTO.getUsername());
|
User user = userMapper.getByUsername(userLoginDTO.getUsername());
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
@@ -104,40 +102,19 @@ public class UserServiceImpl implements UserService {
|
|||||||
// 生成token
|
// 生成token
|
||||||
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
String token = JwtUtil.generateToken(String.valueOf(user.getUserId()));
|
||||||
|
|
||||||
Long userId = user.getUserId();
|
String redisKey = "login:token:" + user.getUserId();
|
||||||
String deviceId = userLoginDTO.getDeviceId();
|
|
||||||
|
|
||||||
// 缓存登录token
|
|
||||||
String tokenKey = "login:token:" + userId+ ":" + deviceId;
|
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
tokenKey,
|
redisKey,
|
||||||
token,
|
token,
|
||||||
7,
|
7,
|
||||||
TimeUnit.DAYS
|
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 userLoginVO = new UserLoginVO();
|
||||||
userLoginVO.setUserId(userId);
|
userLoginVO.setUserId(user.getUserId());
|
||||||
userLoginVO.setNickname(user.getNickname());
|
userLoginVO.setNickname(user.getNickname());
|
||||||
userLoginVO.setToken(token);
|
userLoginVO.setToken(token);
|
||||||
userLoginVO.setDeviceId(deviceId);
|
|
||||||
return userLoginVO;
|
return userLoginVO;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +123,10 @@ public class UserServiceImpl implements UserService {
|
|||||||
* @param token 登录凭证
|
* @param token 登录凭证
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void logout(String token, String deviceId) {
|
public void logout(String token) {
|
||||||
Claims claims = JwtUtil.getClaimsFromToken(token);
|
Claims claims = JwtUtil.getClaimsFromToken(token);
|
||||||
// 获取token信息
|
// 获取token信息
|
||||||
String userId = claims.getSubject();
|
String subject = claims.getSubject();
|
||||||
// 获取token的过期时间
|
// 获取token的过期时间
|
||||||
Date expiration = claims.getExpiration();
|
Date expiration = claims.getExpiration();
|
||||||
// 判断 token 是否已过期
|
// 判断 token 是否已过期
|
||||||
@@ -159,15 +136,10 @@ public class UserServiceImpl implements UserService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从Redis中删除当前设备登录的token记录
|
// 从Redis中删除登录token记录
|
||||||
String loginTokenKey = "login:token:" + userId + ":" + deviceId;
|
String loginTokenKey = "login:token:" + subject;
|
||||||
redisTemplate.delete(loginTokenKey);
|
redisTemplate.delete(loginTokenKey);
|
||||||
|
|
||||||
// 删除设备信息
|
|
||||||
String deviceKey = "user:device:" + userId + ":" + deviceId;
|
|
||||||
redisTemplate.delete(deviceKey);
|
|
||||||
|
|
||||||
// 将token加入黑名单
|
|
||||||
String logoutKey = "jwt:blacklist:" + token;
|
String logoutKey = "jwt:blacklist:" + token;
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
logoutKey,
|
logoutKey,
|
||||||
@@ -528,11 +500,6 @@ public class UserServiceImpl implements UserService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断用户是否在线
|
|
||||||
* @param userId 用户ID
|
|
||||||
* @return true: 在线,false: 离线
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isUserOnline(Long userId) {
|
public boolean isUserOnline(Long userId) {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
@@ -551,4 +518,11 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
return Boolean.TRUE.equals(online);
|
return Boolean.TRUE.equals(online);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<User> searchUsers(String userName, String hobbies) {
|
||||||
|
// 调用Mapper层的查询方法
|
||||||
|
return userMapper.searchUsers(userName, hobbies);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -130,5 +130,17 @@
|
|||||||
FROM user WHERE user_latitude BETWEEN #{minLat} AND #{maxLat} AND user_longitude BETWEEN #{minLng} AND #{maxLng}
|
FROM user WHERE user_latitude BETWEEN #{minLat} AND #{maxLat} AND user_longitude BETWEEN #{minLng} AND #{maxLng}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<!-- 搜索用户:支持用户名模糊匹配、爱好标签精确匹配 -->
|
||||||
|
<select id="searchUsers" resultMap="UserResultMap">
|
||||||
|
SELECT * FROM user
|
||||||
|
WHERE 1=1
|
||||||
|
<!-- 用户名模糊搜索 -->
|
||||||
|
<if test="userName != null and userName != ''">
|
||||||
|
AND user_name LIKE CONCAT('%', #{userName}, '%')
|
||||||
|
</if>
|
||||||
|
<!-- 爱好标签搜索:MySQL 8.0+ 支持JSON_CONTAINS -->
|
||||||
|
<if test="hobbies != null and hobbies != ''">
|
||||||
|
AND JSON_CONTAINS(hobbies, CONCAT('"', #{hobbies}, '"'))
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
Reference in New Issue
Block a user