|
@@ -2,6 +2,10 @@ package com.zhentao.service;
|
|
|
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
import com.zhentao.entity.MatchData;
|
|
import com.zhentao.entity.MatchData;
|
|
|
|
|
+import com.zhentao.entity.User;
|
|
|
|
|
+import com.zhentao.entity.UserProfile;
|
|
|
|
|
+import com.zhentao.mapper.UserMapper;
|
|
|
|
|
+import com.zhentao.mapper.UserProfileMapper;
|
|
|
import com.zhentao.redis.RedisMatchPool;
|
|
import com.zhentao.redis.RedisMatchPool;
|
|
|
import com.zhentao.redis.RedisMatchQueue;
|
|
import com.zhentao.redis.RedisMatchQueue;
|
|
|
import com.zhentao.util.MatchingAlgorithmUtils;
|
|
import com.zhentao.util.MatchingAlgorithmUtils;
|
|
@@ -11,6 +15,9 @@ import org.springframework.data.redis.core.script.DefaultRedisScript;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
import java.util.*;
|
|
import java.util.*;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.time.Period;
|
|
|
|
|
+import java.time.ZoneId;
|
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
@Slf4j
|
|
@Slf4j
|
|
@@ -26,15 +33,21 @@ public class MatchService {
|
|
|
private final ObjectMapper objectMapper;
|
|
private final ObjectMapper objectMapper;
|
|
|
private final RedisMatchQueue matchQueue;
|
|
private final RedisMatchQueue matchQueue;
|
|
|
private final RedisTemplate<String, Object> redisTemplate;
|
|
private final RedisTemplate<String, Object> redisTemplate;
|
|
|
|
|
+ private final UserMapper userMapper;
|
|
|
|
|
+ private final UserProfileMapper userProfileMapper;
|
|
|
private final java.util.concurrent.ConcurrentHashMap<String, Long> localLocks = new java.util.concurrent.ConcurrentHashMap<>();
|
|
private final java.util.concurrent.ConcurrentHashMap<String, Long> localLocks = new java.util.concurrent.ConcurrentHashMap<>();
|
|
|
|
|
|
|
|
public MatchService(RedisMatchPool redisMatchPool,
|
|
public MatchService(RedisMatchPool redisMatchPool,
|
|
|
ObjectMapper objectMapper, RedisMatchQueue matchQueue,
|
|
ObjectMapper objectMapper, RedisMatchQueue matchQueue,
|
|
|
- RedisTemplate<String, Object> redisTemplate) {
|
|
|
|
|
|
|
+ RedisTemplate<String, Object> redisTemplate,
|
|
|
|
|
+ UserMapper userMapper,
|
|
|
|
|
+ UserProfileMapper userProfileMapper) {
|
|
|
this.redisMatchPool = redisMatchPool;
|
|
this.redisMatchPool = redisMatchPool;
|
|
|
this.objectMapper = objectMapper;
|
|
this.objectMapper = objectMapper;
|
|
|
this.matchQueue = matchQueue;
|
|
this.matchQueue = matchQueue;
|
|
|
this.redisTemplate = redisTemplate;
|
|
this.redisTemplate = redisTemplate;
|
|
|
|
|
+ this.userMapper = userMapper;
|
|
|
|
|
+ this.userProfileMapper = userProfileMapper;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public Map<String, Object> handleMatchRequest(MatchData data) {
|
|
public Map<String, Object> handleMatchRequest(MatchData data) {
|
|
@@ -79,6 +92,20 @@ public class MatchService {
|
|
|
}
|
|
}
|
|
|
double threshold = getMatchThreshold(matchMode);
|
|
double threshold = getMatchThreshold(matchMode);
|
|
|
|
|
|
|
|
|
|
+ // 先尝试从匹配池中匹配
|
|
|
|
|
+ MatchResult poolResult = tryMatchFromPool(userId, userData, matchMode, threshold);
|
|
|
|
|
+ if (poolResult.isMatched()) {
|
|
|
|
|
+ return poolResult;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果匹配池中没有合适的,从数据库中的所有用户中匹配
|
|
|
|
|
+ return tryMatchFromDatabase(userId, userData, matchMode, threshold);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从匹配池中尝试匹配
|
|
|
|
|
+ */
|
|
|
|
|
+ private MatchResult tryMatchFromPool(String userId, MatchData userData, String matchMode, double threshold) {
|
|
|
List<String> online = redisMatchPool.getAllUsersInPool();
|
|
List<String> online = redisMatchPool.getAllUsersInPool();
|
|
|
Set<String> unmatched = redisMatchPool.getUserUnmatchedSet(userId);
|
|
Set<String> unmatched = redisMatchPool.getUserUnmatchedSet(userId);
|
|
|
Map<String, Double> scores = new HashMap<>();
|
|
Map<String, Double> scores = new HashMap<>();
|
|
@@ -124,6 +151,136 @@ public class MatchService {
|
|
|
return new MatchResult(false, null, 0);
|
|
return new MatchResult(false, null, 0);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从数据库中的所有已注册用户中匹配
|
|
|
|
|
+ */
|
|
|
|
|
+ private MatchResult tryMatchFromDatabase(String userId, MatchData userData, String matchMode, double threshold) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 获取异性用户(只匹配异性)
|
|
|
|
|
+ Integer targetGender = userData.getGender() == 1 ? 2 : 1;
|
|
|
|
|
+ List<User> allUsers = userMapper.getUsersByGender(targetGender);
|
|
|
|
|
+
|
|
|
|
|
+ if (allUsers == null || allUsers.isEmpty()) {
|
|
|
|
|
+ log.info("数据库中没有找到异性用户");
|
|
|
|
|
+ return new MatchResult(false, null, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Set<String> unmatched = redisMatchPool.getUserUnmatchedSet(userId);
|
|
|
|
|
+ Map<String, Double> scores = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (User user : allUsers) {
|
|
|
|
|
+ String otherUserId = String.valueOf(user.getUserId());
|
|
|
|
|
+
|
|
|
|
|
+ // 排除自己
|
|
|
|
|
+ if (userId.equals(otherUserId)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 排除已经不匹配的用户
|
|
|
|
|
+ if (unmatched.contains(otherUserId)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 构建MatchData
|
|
|
|
|
+ MatchData otherData = buildMatchDataFromUser(user);
|
|
|
|
|
+ if (otherData == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ // 根据匹配模式进行额外筛选
|
|
|
|
|
+ if (!shouldMatchByMode(userData, otherData, matchMode)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算匹配分数
|
|
|
|
|
+ double score = MatchingAlgorithmUtils.calculateMatchScore(userData, otherData);
|
|
|
|
|
+ scores.put(otherUserId, score);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果分数达到阈值,立即返回匹配成功
|
|
|
|
|
+ if (score >= threshold) {
|
|
|
|
|
+ log.info("从数据库匹配成功: userId={}, matchedUserId={}, score={}", userId, otherUserId, score);
|
|
|
|
|
+ return new MatchResult(true, otherUserId, score);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果没有达到阈值的,返回分数最高的
|
|
|
|
|
+ if (!scores.isEmpty()) {
|
|
|
|
|
+ Map.Entry<String, Double> best = scores.entrySet().stream()
|
|
|
|
|
+ .max(Map.Entry.comparingByValue())
|
|
|
|
|
+ .get();
|
|
|
|
|
+
|
|
|
|
|
+ // 降低阈值要求,只要分数大于30就可以匹配
|
|
|
|
|
+ if (best.getValue() >= 30.0) {
|
|
|
|
|
+ log.info("从数据库匹配成功(降低阈值): userId={}, matchedUserId={}, score={}",
|
|
|
|
|
+ userId, best.getKey(), best.getValue());
|
|
|
|
|
+ return new MatchResult(true, best.getKey(), best.getValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 记录最佳但未匹配的用户
|
|
|
|
|
+ redisMatchPool.recordUnmatch(userId, best.getKey());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ log.info("数据库中未找到合适的匹配对象: userId={}", userId);
|
|
|
|
|
+ return new MatchResult(false, null, 0);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("从数据库匹配时发生错误", e);
|
|
|
|
|
+ return new MatchResult(false, null, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从User和UserProfile构建MatchData
|
|
|
|
|
+ */
|
|
|
|
|
+ private MatchData buildMatchDataFromUser(User user) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ MatchData data = new MatchData();
|
|
|
|
|
+ data.setUserId(String.valueOf(user.getUserId()));
|
|
|
|
|
+ data.setNickname(user.getNickname());
|
|
|
|
|
+ data.setGender(user.getGender());
|
|
|
|
|
+ data.setAvatarUrl(user.getAvatarUrl());
|
|
|
|
|
+
|
|
|
|
|
+ // 计算年龄
|
|
|
|
|
+ if (user.getBirthDate() != null) {
|
|
|
|
|
+ LocalDate birthDate = user.getBirthDate().toInstant()
|
|
|
|
|
+ .atZone(ZoneId.systemDefault()).toLocalDate();
|
|
|
|
|
+ int age = Period.between(birthDate, LocalDate.now()).getYears();
|
|
|
|
|
+ data.setAge(age);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取用户资料
|
|
|
|
|
+ UserProfile profile = userProfileMapper.getByUserId(user.getUserId());
|
|
|
|
|
+ if (profile != null) {
|
|
|
|
|
+ // 设置兴趣爱好
|
|
|
|
|
+ if (profile.getHobby() != null) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (profile.getHobby() instanceof String) {
|
|
|
|
|
+ String hobbyStr = (String) profile.getHobby();
|
|
|
|
|
+ List<String> interests = objectMapper.readValue(hobbyStr, List.class);
|
|
|
|
|
+ data.setInterests(interests);
|
|
|
|
|
+ } else if (profile.getHobby() instanceof List) {
|
|
|
|
|
+ data.setInterests((List<String>) profile.getHobby());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("解析兴趣爱好失败: userId={}", user.getUserId());
|
|
|
|
|
+ data.setInterests(new ArrayList<>());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data.setInterests(new ArrayList<>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置城市信息(使用cityId)
|
|
|
|
|
+ if (profile.getCityId() != null) {
|
|
|
|
|
+ data.setCity("城市" + profile.getCityId());
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ data.setInterests(new ArrayList<>());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置默认简介
|
|
|
|
|
+ data.setIntroduction("来自数据库的用户");
|
|
|
|
|
+
|
|
|
|
|
+ return data;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("构建MatchData失败: userId={}", user.getUserId(), e);
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 根据匹配模式获取阈值
|
|
* 根据匹配模式获取阈值
|
|
|
*/
|
|
*/
|
|
@@ -259,8 +416,10 @@ public class MatchService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private Map<String, Object> buildUserInfo(String userId) {
|
|
private Map<String, Object> buildUserInfo(String userId) {
|
|
|
|
|
+ // 先尝试从匹配池获取
|
|
|
MatchData data = redisMatchPool.getUserMatchData(userId);
|
|
MatchData data = redisMatchPool.getUserMatchData(userId);
|
|
|
Map<String,Object> m = new HashMap<>();
|
|
Map<String,Object> m = new HashMap<>();
|
|
|
|
|
+
|
|
|
if (data != null) {
|
|
if (data != null) {
|
|
|
m.put("userId", data.getUserId());
|
|
m.put("userId", data.getUserId());
|
|
|
m.put("nickname", data.getNickname());
|
|
m.put("nickname", data.getNickname());
|
|
@@ -270,6 +429,27 @@ public class MatchService {
|
|
|
m.put("interests", data.getInterests());
|
|
m.put("interests", data.getInterests());
|
|
|
m.put("city", data.getCity());
|
|
m.put("city", data.getCity());
|
|
|
m.put("introduction", data.getIntroduction());
|
|
m.put("introduction", data.getIntroduction());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果匹配池中没有,从数据库获取
|
|
|
|
|
+ try {
|
|
|
|
|
+ Integer userIdInt = Integer.parseInt(userId);
|
|
|
|
|
+ User user = userMapper.selectById(userIdInt);
|
|
|
|
|
+ if (user != null) {
|
|
|
|
|
+ MatchData dbData = buildMatchDataFromUser(user);
|
|
|
|
|
+ if (dbData != null) {
|
|
|
|
|
+ m.put("userId", dbData.getUserId());
|
|
|
|
|
+ m.put("nickname", dbData.getNickname());
|
|
|
|
|
+ m.put("gender", dbData.getGender());
|
|
|
|
|
+ m.put("age", dbData.getAge());
|
|
|
|
|
+ m.put("avatarUrl", dbData.getAvatarUrl());
|
|
|
|
|
+ m.put("interests", dbData.getInterests());
|
|
|
|
|
+ m.put("city", dbData.getCity());
|
|
|
|
|
+ m.put("introduction", dbData.getIntroduction());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("从数据库获取用户信息失败: userId={}", userId, e);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
return m;
|
|
return m;
|
|
|
}
|
|
}
|