|
|
|
|
@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.data.domain.Sort;
|
|
|
|
|
import org.springframework.data.mongodb.core.MongoTemplate;
|
|
|
|
|
import org.springframework.data.mongodb.core.aggregation.*;
|
|
|
|
|
import org.springframework.data.mongodb.core.query.Criteria;
|
|
|
|
|
import org.springframework.data.mongodb.core.query.Query;
|
|
|
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
|
@ -28,215 +29,148 @@ public class PlayBackLogRepositoryImpl implements PlayBackLogRepository {
|
|
|
|
|
@Override
|
|
|
|
|
public PageResult<PlayBackLog> findPlayBackLogsByCondition(PlayBackLogQueryDto queryDto) {
|
|
|
|
|
try {
|
|
|
|
|
// 先查询基础条件(不包括玩家条件)
|
|
|
|
|
Query baseQuery = buildBaseQuery(queryDto);
|
|
|
|
|
|
|
|
|
|
log.info("基础查询条件:{}", baseQuery.toString());
|
|
|
|
|
|
|
|
|
|
// 查询所有记录
|
|
|
|
|
List<PlayBackLog> allRecords = mongoTemplate.find(baseQuery, PlayBackLog.class);
|
|
|
|
|
log.info("基础查询到记录数:{}", allRecords.size());
|
|
|
|
|
|
|
|
|
|
// 打印调试信息
|
|
|
|
|
if (!allRecords.isEmpty() && StringUtils.hasText(queryDto.getPlayerId())) {
|
|
|
|
|
PlayBackLog firstRecord = allRecords.get(0);
|
|
|
|
|
Map<String, PlayBackLog.PlayerSnapshot> playerInfo = firstRecord.getPlayerInfo();
|
|
|
|
|
if (playerInfo != null) {
|
|
|
|
|
log.info("第一条记录的playerInfo结构:");
|
|
|
|
|
playerInfo.forEach((key, player) -> {
|
|
|
|
|
if (player != null) {
|
|
|
|
|
log.info("Key: {}, 原始Player.id: {}, Player.userId: {}, Player.name: {}",
|
|
|
|
|
key, player.getId(), player.getUserId(), player.getName());
|
|
|
|
|
// 1. 构建基础匹配条件
|
|
|
|
|
Criteria baseCriteria = buildBaseCriteria(queryDto);
|
|
|
|
|
|
|
|
|
|
// 2. 检查是否有玩家相关条件
|
|
|
|
|
boolean hasPlayerCondition = StringUtils.hasText(queryDto.getPlayerId())
|
|
|
|
|
|| StringUtils.hasText(queryDto.getPlayerName())
|
|
|
|
|
|| queryDto.getUserId() != null;
|
|
|
|
|
|
|
|
|
|
// 3. 构建聚合管道
|
|
|
|
|
List<AggregationOperation> operations = new ArrayList<>();
|
|
|
|
|
operations.add(Aggregation.match(baseCriteria));
|
|
|
|
|
|
|
|
|
|
if (hasPlayerCondition) {
|
|
|
|
|
// 将 playerInfo Map 转为数组以便查询
|
|
|
|
|
operations.add(Aggregation.project(PlayBackLog.class)
|
|
|
|
|
.andExpression("{$objectToArray: '$playerInfo'}").as("playerInfoList"));
|
|
|
|
|
|
|
|
|
|
Criteria playerCriteria = new Criteria();
|
|
|
|
|
|
|
|
|
|
// --- 核心修复开始 ---
|
|
|
|
|
|
|
|
|
|
// 1. 处理 玩家昵称 (playerName) - 模糊查询
|
|
|
|
|
if (StringUtils.hasText(queryDto.getPlayerName())) {
|
|
|
|
|
// 对应数据库中的 playerInfo.value.name
|
|
|
|
|
playerCriteria.and("playerInfoList.v.name").regex(queryDto.getPlayerName(), "i");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 处理 玩家ID (playerId) - 智能匹配 UUID 或 UserId
|
|
|
|
|
if (StringUtils.hasText(queryDto.getPlayerId())) {
|
|
|
|
|
String inputId = queryDto.getPlayerId();
|
|
|
|
|
List<Criteria> idOrList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
// 情况A: 输入的是 UUID (字符串匹配 Key 或 v.id)
|
|
|
|
|
idOrList.add(Criteria.where("playerInfoList.k").is(inputId));
|
|
|
|
|
idOrList.add(Criteria.where("playerInfoList.v.id").is(inputId));
|
|
|
|
|
|
|
|
|
|
// 情况B: 输入的是 数字UserId (尝试转为Long进行数字匹配)
|
|
|
|
|
// 这一点至关重要,因为DB里 userId 是数字,前端传的是字符串
|
|
|
|
|
if (inputId.matches("\\d+")) { // 如果输入全是数字
|
|
|
|
|
try {
|
|
|
|
|
long uid = Long.parseLong(inputId);
|
|
|
|
|
idOrList.add(Criteria.where("playerInfoList.v.userId").is(uid));
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
// 忽略转换错误
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用 OR 连接所有可能的 ID 匹配情况
|
|
|
|
|
playerCriteria.andOperator(new Criteria().orOperator(idOrList.toArray(new Criteria[0])));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 内存中过滤玩家条件(注意:先不调用fixPlayerInfoIds)
|
|
|
|
|
List<PlayBackLog> filteredRecords = filterByPlayerCondition(allRecords, queryDto);
|
|
|
|
|
log.info("玩家条件过滤后记录数:{}", filteredRecords.size());
|
|
|
|
|
// 3. 处理 明确的 UserId (保留原有逻辑,防守性编程)
|
|
|
|
|
if (queryDto.getUserId() != null) {
|
|
|
|
|
playerCriteria.and("playerInfoList.v.userId").is(queryDto.getUserId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算分页
|
|
|
|
|
long total = filteredRecords.size();
|
|
|
|
|
int skip = (queryDto.getPage() - 1) * queryDto.getSize();
|
|
|
|
|
int end = Math.min(skip + queryDto.getSize(), filteredRecords.size());
|
|
|
|
|
// --- 核心修复结束 ---
|
|
|
|
|
|
|
|
|
|
// 获取分页数据
|
|
|
|
|
List<PlayBackLog> pageList = new ArrayList<>();
|
|
|
|
|
for (int i = skip; i < end; i++) {
|
|
|
|
|
pageList.add(filteredRecords.get(i));
|
|
|
|
|
operations.add(Aggregation.match(playerCriteria));
|
|
|
|
|
operations.add(Aggregation.project().andExclude("playerInfoList"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 修复playerInfo的ID(在过滤之后)
|
|
|
|
|
fixPlayerInfoIds(pageList);
|
|
|
|
|
// 4. 获取总数 (Count)
|
|
|
|
|
List<AggregationOperation> countOps = new ArrayList<>(operations);
|
|
|
|
|
countOps.add(Aggregation.count().as("total"));
|
|
|
|
|
Aggregation countAgg = Aggregation.newAggregation(countOps);
|
|
|
|
|
AggregationResults<Map> countResult = mongoTemplate.aggregate(countAgg, PlayBackLog.class, Map.class);
|
|
|
|
|
|
|
|
|
|
long total = 0;
|
|
|
|
|
if (!countResult.getMappedResults().isEmpty()) {
|
|
|
|
|
Object totalVal = countResult.getMappedResults().get(0).get("total");
|
|
|
|
|
total = totalVal != null ? Long.parseLong(totalVal.toString()) : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.info("查询牌局记录成功 - 总数: {}, 当前页: {}, 页面大小: {}",
|
|
|
|
|
total, queryDto.getPage(), queryDto.getSize());
|
|
|
|
|
log.info("查询记录数: {}", total);
|
|
|
|
|
|
|
|
|
|
return new PageResult<>(total, queryDto.getPage(), queryDto.getSize(), pageList);
|
|
|
|
|
// 5. 获取分页数据
|
|
|
|
|
if (total > 0) {
|
|
|
|
|
operations.add(Aggregation.sort(Sort.by(Sort.Direction.DESC, "gameTimeMillis")));
|
|
|
|
|
long skip = (long) (queryDto.getPage() - 1) * queryDto.getSize();
|
|
|
|
|
operations.add(Aggregation.skip(skip));
|
|
|
|
|
operations.add(Aggregation.limit(queryDto.getSize()));
|
|
|
|
|
|
|
|
|
|
Aggregation dataAgg = Aggregation.newAggregation(operations);
|
|
|
|
|
// 修复这里: 显式指定 inputType 和 outputType
|
|
|
|
|
AggregationResults<PlayBackLog> result = mongoTemplate.aggregate(dataAgg, PlayBackLog.class, PlayBackLog.class);
|
|
|
|
|
|
|
|
|
|
List<PlayBackLog> pageList = result.getMappedResults();
|
|
|
|
|
fixPlayerInfoIds(pageList); // 别忘了修复ID的辅助方法
|
|
|
|
|
|
|
|
|
|
return new PageResult<>(total, queryDto.getPage(), queryDto.getSize(), pageList);
|
|
|
|
|
} else {
|
|
|
|
|
return new PageResult<>(0, queryDto.getPage(), queryDto.getSize(), new ArrayList<>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("查询牌局记录失败", e);
|
|
|
|
|
throw new RuntimeException("数据库查询异常: " + e.getMessage(), e);
|
|
|
|
|
log.error("查询失败", e);
|
|
|
|
|
throw new RuntimeException("DB Error: " + e.getMessage(), e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建基础查询条件(不包括玩家条件)
|
|
|
|
|
* 构建基础 Criteria (不包含聚合管道特有的玩家逻辑)
|
|
|
|
|
*/
|
|
|
|
|
private Query buildBaseQuery(PlayBackLogQueryDto queryDto) {
|
|
|
|
|
Query query = new Query();
|
|
|
|
|
private Criteria buildBaseCriteria(PlayBackLogQueryDto queryDto) {
|
|
|
|
|
List<Criteria> criteriaList = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
// 时间范围查询
|
|
|
|
|
if (queryDto.getStartTime() != null || queryDto.getEndTime() != null) {
|
|
|
|
|
Criteria timeCriteria = Criteria.where("gameTimeMillis");
|
|
|
|
|
|
|
|
|
|
if (queryDto.getStartTime() != null) {
|
|
|
|
|
long startMillis = queryDto.getStartTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
|
|
|
|
timeCriteria.gte(startMillis);
|
|
|
|
|
log.info("开始时间:{} -> {}", queryDto.getStartTime(), startMillis);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (queryDto.getEndTime() != null) {
|
|
|
|
|
long endMillis = queryDto.getEndTime().toInstant(ZoneOffset.of("+8")).toEpochMilli();
|
|
|
|
|
timeCriteria.lte(endMillis);
|
|
|
|
|
log.info("结束时间:{} -> {}", queryDto.getEndTime(), endMillis);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
criteriaList.add(timeCriteria);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 游戏模式查询
|
|
|
|
|
// 基础字段匹配
|
|
|
|
|
if (queryDto.getGameMode() != null) {
|
|
|
|
|
criteriaList.add(Criteria.where("gameMode").is(queryDto.getGameMode()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 游戏类型查询
|
|
|
|
|
if (queryDto.getGameType() != null) {
|
|
|
|
|
criteriaList.add(Criteria.where("gameType").is(queryDto.getGameType()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 房间ID查询
|
|
|
|
|
if (queryDto.getRoomId() != null) {
|
|
|
|
|
criteriaList.add(Criteria.where("roomId").is(queryDto.getRoomId()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 组合条件
|
|
|
|
|
if (!criteriaList.isEmpty()) {
|
|
|
|
|
if (criteriaList.size() == 1) {
|
|
|
|
|
query.addCriteria(criteriaList.get(0));
|
|
|
|
|
} else {
|
|
|
|
|
query.addCriteria(new Criteria().andOperator(criteriaList.toArray(new Criteria[0])));
|
|
|
|
|
}
|
|
|
|
|
if (criteriaList.isEmpty()) {
|
|
|
|
|
return new Criteria();
|
|
|
|
|
} else if (criteriaList.size() == 1) {
|
|
|
|
|
return criteriaList.get(0);
|
|
|
|
|
} else {
|
|
|
|
|
return new Criteria().andOperator(criteriaList.toArray(new Criteria[0]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按时间倒序排列
|
|
|
|
|
query.with(Sort.by(Sort.Direction.DESC, "gameTimeMillis"));
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 内存中过滤玩家条件
|
|
|
|
|
*/
|
|
|
|
|
private List<PlayBackLog> filterByPlayerCondition(List<PlayBackLog> logs, PlayBackLogQueryDto queryDto) {
|
|
|
|
|
if (logs == null || logs.isEmpty()) {
|
|
|
|
|
return new ArrayList<>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果没有玩家查询条件,返回所有
|
|
|
|
|
if (!StringUtils.hasText(queryDto.getPlayerId()) &&
|
|
|
|
|
!StringUtils.hasText(queryDto.getPlayerName()) &&
|
|
|
|
|
queryDto.getUserId() == null) {
|
|
|
|
|
return logs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<PlayBackLog> result = new ArrayList<>();
|
|
|
|
|
for (PlayBackLog logRecord : logs) {
|
|
|
|
|
if (matchesPlayerCondition(logRecord, queryDto)) {
|
|
|
|
|
result.add(logRecord);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断单条记录是否匹配玩家条件 - 修正版本
|
|
|
|
|
*/
|
|
|
|
|
private boolean matchesPlayerCondition(PlayBackLog logRecord, PlayBackLogQueryDto queryDto) {
|
|
|
|
|
Map<String, PlayBackLog.PlayerSnapshot> playerInfo = logRecord.getPlayerInfo();
|
|
|
|
|
if (playerInfo == null || playerInfo.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否有玩家匹配所有条件
|
|
|
|
|
for (Map.Entry<String, PlayBackLog.PlayerSnapshot> entry : playerInfo.entrySet()) {
|
|
|
|
|
String mapKey = entry.getKey(); // Map的key,如"r:309dd5e6-547a-4f2a-9db4-403e8f91185e"
|
|
|
|
|
PlayBackLog.PlayerSnapshot player = entry.getValue();
|
|
|
|
|
|
|
|
|
|
if (player != null && matchesAllPlayerConditions(player, mapKey, queryDto)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断单个玩家是否匹配所有条件 - 修正版本
|
|
|
|
|
* 需要同时检查Map的key和PlayerSnapshot.id
|
|
|
|
|
*/
|
|
|
|
|
private boolean matchesAllPlayerConditions(PlayBackLog.PlayerSnapshot player, String mapKey, PlayBackLogQueryDto queryDto) {
|
|
|
|
|
// 用于调试
|
|
|
|
|
log.debug("检查玩家: mapKey={}, player.id={}, player.userId={}, player.name={}",
|
|
|
|
|
mapKey, player.getId(), player.getUserId(), player.getName());
|
|
|
|
|
|
|
|
|
|
// playerId查询 - 需要同时检查Map的key和PlayerSnapshot.id
|
|
|
|
|
if (StringUtils.hasText(queryDto.getPlayerId())) {
|
|
|
|
|
String playerIdToMatch = queryDto.getPlayerId();
|
|
|
|
|
|
|
|
|
|
// 检查1:Map的key是否匹配
|
|
|
|
|
boolean keyMatches = playerIdToMatch.equals(mapKey);
|
|
|
|
|
|
|
|
|
|
// 检查2:PlayerSnapshot.id是否匹配
|
|
|
|
|
boolean idMatches = playerIdToMatch.equals(player.getId());
|
|
|
|
|
|
|
|
|
|
// 如果有一个匹配就返回true
|
|
|
|
|
boolean playerIdMatches = keyMatches || idMatches;
|
|
|
|
|
|
|
|
|
|
log.debug("PlayerId匹配检查: 查询playerId={}, mapKey={}, 玩家id={}, keyMatches={}, idMatches={}, 最终结果={}",
|
|
|
|
|
playerIdToMatch, mapKey, player.getId(), keyMatches, idMatches, playerIdMatches);
|
|
|
|
|
|
|
|
|
|
if (!playerIdMatches) {
|
|
|
|
|
return false; // playerId不匹配,直接返回false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// userId查询
|
|
|
|
|
if (queryDto.getUserId() != null) {
|
|
|
|
|
boolean userIdMatches = player.getUserId() != null &&
|
|
|
|
|
queryDto.getUserId().equals(player.getUserId());
|
|
|
|
|
log.debug("UserId匹配检查: 查询userId={}, 玩家userId={}, 匹配结果={}",
|
|
|
|
|
queryDto.getUserId(), player.getUserId(), userIdMatches);
|
|
|
|
|
if (!userIdMatches) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// playerName模糊查询
|
|
|
|
|
if (StringUtils.hasText(queryDto.getPlayerName())) {
|
|
|
|
|
boolean nameMatches = player.getName() != null &&
|
|
|
|
|
player.getName().toLowerCase().contains(queryDto.getPlayerName().toLowerCase());
|
|
|
|
|
log.debug("PlayerName匹配检查: 查询name={}, 玩家name={}, 匹配结果={}",
|
|
|
|
|
queryDto.getPlayerName(), player.getName(), nameMatches);
|
|
|
|
|
if (!nameMatches) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修复playerInfo的ID与key不一致的问题
|
|
|
|
|
|