新增记录用户游玩记录接口

This commit is contained in:
tianfeng 2026-03-11 20:01:31 +08:00
parent 020fc3e543
commit 4f53c6a9e8
10 changed files with 356 additions and 0 deletions

View File

@ -1,5 +1,6 @@
package com.red.circle.other.adapter.app.game;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import com.red.circle.common.business.dto.cmd.app.AppIdLongCmd;
import com.red.circle.common.business.dto.cmd.app.AppRoomIdCmd;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
@ -8,7 +9,10 @@ import com.red.circle.other.app.dto.cmd.game.GameLudoCreateCmd;
import com.red.circle.other.app.dto.cmd.game.GameLudoEndCmd;
import com.red.circle.other.app.dto.cmd.game.GameLudoJoinCmd;
import com.red.circle.other.app.dto.cmd.game.GameLudoStartCmd;
import com.red.circle.other.app.dto.clientobject.game.GamePlayHistoryCO;
import com.red.circle.other.app.dto.cmd.game.GamePlayHistoryRecordCmd;
import com.red.circle.other.app.service.game.GameLudoService;
import com.red.circle.other.app.service.game.GamePlayHistoryService;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import java.util.Arrays;
@ -39,6 +43,7 @@ import org.springframework.web.bind.annotation.RestController;
public class GameLudoAppRestController {
private final GameLudoService gameLudoService;
private final GamePlayHistoryService gamePlayHistoryService;
private final UserProfileGateway userProfileGateway;
private final UserProfileAppConvertor userProfileAppConvertor;
@ -161,7 +166,32 @@ public class GameLudoAppRestController {
return userProfileAppConvertor.toUserProfileDTO(
userProfileGateway.getByUserId(userId)
);
}
/**
* 记录游玩历史.
*
* @eo.name 记录游玩历史
* @eo.url /history/record
* @eo.method post
* @eo.request-type json
*/
@PostMapping("/history/record")
public void recordHistory(@RequestBody @Validated GamePlayHistoryRecordCmd cmd) {
gamePlayHistoryService.record(cmd);
}
/**
* 查询最近游玩历史最多5条.
*
* @eo.name 查询最近游玩历史
* @eo.url /history/recent
* @eo.method get
* @eo.request-type formdata
*/
@GetMapping("/history/recent")
public List<GamePlayHistoryCO> listRecentHistory(@Validated AppExtCommand cmd) {
return gamePlayHistoryService.listRecent(cmd.requireReqSysOrigin(), cmd.requiredReqUserId());
}
}

View File

@ -0,0 +1,46 @@
package com.red.circle.other.app.command.game;
import com.red.circle.other.app.dto.clientobject.game.GamePlayHistoryCO;
import com.red.circle.other.infra.database.mongo.entity.game.UserGamePlayHistory;
import com.red.circle.other.infra.database.mongo.service.game.UserGamePlayHistoryService;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 查询用户游玩历史.
*
* @author tf
*/
@Component
@RequiredArgsConstructor
public class GamePlayHistoryQryExe {
private static final int RECENT_LIMIT = 5;
private final UserGamePlayHistoryService userGamePlayHistoryService;
public List<GamePlayHistoryCO> execute(String sysOrigin, Long userId) {
List<UserGamePlayHistory> historyList =
userGamePlayHistoryService.listRecent(sysOrigin, userId, RECENT_LIMIT);
return historyList.stream()
.filter(h -> Objects.nonNull(h.getGameInfo()))
.map(this::toCO)
.collect(Collectors.toList());
}
private GamePlayHistoryCO toCO(UserGamePlayHistory history) {
UserGamePlayHistory.GameInfo info = history.getGameInfo();
GamePlayHistoryCO co = new GamePlayHistoryCO();
co.setId(info.getId());
co.setGameId(info.getGameId());
co.setName(info.getName());
co.setCover(info.getCover());
co.setGameCode(info.getGameCode());
co.setPlayTime(history.getPlayTime());
return co;
}
}

View File

@ -0,0 +1,49 @@
package com.red.circle.other.app.command.game;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.dto.cmd.game.GamePlayHistoryRecordCmd;
import com.red.circle.other.infra.database.mongo.entity.game.UserGamePlayHistory;
import com.red.circle.other.infra.database.mongo.entity.game.UserGamePlayHistory.GameInfo;
import com.red.circle.other.infra.database.mongo.service.game.UserGamePlayHistoryService;
import com.red.circle.other.infra.database.rds.entity.sys.GameListConfig;
import com.red.circle.other.infra.database.rds.service.sys.GameListConfigService;
import com.red.circle.other.inner.asserts.GameErrorCode;
import com.red.circle.tool.core.date.TimestampUtils;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 记录用户游玩历史.
*
* @author tf
*/
@Component
@RequiredArgsConstructor
public class GamePlayHistoryRecordCmdExe {
private final GameListConfigService gameListConfigService;
private final UserGamePlayHistoryService userGamePlayHistoryService;
public void execute(GamePlayHistoryRecordCmd cmd) {
GameListConfig config = gameListConfigService.getById(cmd.getGameListConfigId());
ResponseAssert.notNull(GameErrorCode.GAME_NOT_EXIST, config);
GameInfo gameInfo = new GameInfo();
gameInfo.setId(config.getId());
gameInfo.setGameId(config.getGameId());
gameInfo.setName(config.getName());
gameInfo.setCover(config.getCover());
gameInfo.setGameCode(config.getGameCode());
UserGamePlayHistory history = new UserGamePlayHistory();
history.setSysOrigin(cmd.getReqSysOrigin().getOrigin());
history.setUserId(cmd.requiredReqUserId());
history.setGameListConfigId(cmd.getGameListConfigId());
history.setGameInfo(gameInfo);
history.setPlayTime(TimestampUtils.now());
history.setExpiredTime(TimestampUtils.nowPlusDays(90));
userGamePlayHistoryService.record(history);
}
}

View File

@ -0,0 +1,30 @@
package com.red.circle.other.app.service.game;
import com.red.circle.other.app.command.game.GamePlayHistoryQryExe;
import com.red.circle.other.app.command.game.GamePlayHistoryRecordCmdExe;
import com.red.circle.other.app.dto.clientobject.game.GamePlayHistoryCO;
import com.red.circle.other.app.dto.cmd.game.GamePlayHistoryRecordCmd;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author tf
*/
@Service
@RequiredArgsConstructor
public class GamePlayHistoryServiceImpl implements GamePlayHistoryService {
private final GamePlayHistoryRecordCmdExe gamePlayHistoryRecordCmdExe;
private final GamePlayHistoryQryExe gamePlayHistoryQryExe;
@Override
public void record(GamePlayHistoryRecordCmd cmd) {
gamePlayHistoryRecordCmdExe.execute(cmd);
}
@Override
public List<GamePlayHistoryCO> listRecent(String sysOrigin, Long userId) {
return gamePlayHistoryQryExe.execute(sysOrigin, userId);
}
}

View File

@ -0,0 +1,28 @@
package com.red.circle.other.app.dto.clientobject.game;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import java.sql.Timestamp;
import lombok.Data;
/**
* 用户游戏游玩历史.
*
* @author tf
*/
@Data
public class GamePlayHistoryCO {
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String gameId;
private String name;
private String cover;
private String gameCode;
private Timestamp playTime;
}

View File

@ -0,0 +1,20 @@
package com.red.circle.other.app.dto.cmd.game;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 记录用户游玩游戏.
*
* @author tf
*/
@Data
@EqualsAndHashCode(callSuper = false)
public class GamePlayHistoryRecordCmd extends AppExtCommand {
/** GameListConfig 主键 */
@NotNull(message = "gameListConfigId required.")
private Long gameListConfigId;
}

View File

@ -0,0 +1,17 @@
package com.red.circle.other.app.service.game;
import com.red.circle.other.app.dto.clientobject.game.GamePlayHistoryCO;
import com.red.circle.other.app.dto.cmd.game.GamePlayHistoryRecordCmd;
import java.util.List;
/**
* 游戏游玩历史服务.
*
* @author tf
*/
public interface GamePlayHistoryService {
void record(GamePlayHistoryRecordCmd cmd);
List<GamePlayHistoryCO> listRecent(String sysOrigin, Long userId);
}

View File

@ -0,0 +1,58 @@
package com.red.circle.other.infra.database.mongo.entity.game;
import java.io.Serializable;
import java.sql.Timestamp;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* 用户游戏游玩历史.
*
* @author tf
*/
@Data
@Accessors(chain = true)
@Document("user_game_play_history")
@CompoundIndexes({
@CompoundIndex(name = "idx_user_play_time", def = "{'userId': 1, 'playTime': -1}"),
@CompoundIndex(name = "idx_user_game", def = "{'userId': 1, 'gameListConfigId': 1}", unique = true)
})
public class UserGamePlayHistory implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private String id;
private String sysOrigin;
private Long userId;
/** GameListConfig 主键 */
private Long gameListConfigId;
/** 游戏信息快照 */
private GameInfo gameInfo;
/** 最近游玩时间 */
@Indexed
private Timestamp playTime;
/** 过期时间90天TTL自动清理 */
@Indexed(expireAfterSeconds = 0)
private Timestamp expiredTime;
@Data
public static class GameInfo {
private Long id;
private String gameId;
private String name;
private String cover;
private String gameCode;
}
}

View File

@ -0,0 +1,22 @@
package com.red.circle.other.infra.database.mongo.service.game;
import com.red.circle.other.infra.database.mongo.entity.game.UserGamePlayHistory;
import java.util.List;
/**
* 用户游戏游玩历史服务.
*
* @author tf
*/
public interface UserGamePlayHistoryService {
/**
* 记录游玩同一用户同一游戏 upsert仅更新 playTime 和快照.
*/
void record(UserGamePlayHistory history);
/**
* 查询用户最近游玩的游戏 playTime 倒序最多 limit .
*/
List<UserGamePlayHistory> listRecent(String sysOrigin, Long userId, int limit);
}

View File

@ -0,0 +1,56 @@
package com.red.circle.other.infra.database.mongo.service.game.impl;
import com.red.circle.other.infra.database.mongo.entity.game.UserGamePlayHistory;
import com.red.circle.other.infra.database.mongo.service.game.UserGamePlayHistoryService;
import com.red.circle.tool.core.date.TimestampUtils;
import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
/**
* @author tf
*/
@Service
@RequiredArgsConstructor
public class UserGamePlayHistoryServiceImpl implements UserGamePlayHistoryService {
private final MongoTemplate mongoTemplate;
@Override
public void record(UserGamePlayHistory history) {
Query query = Query.query(
Criteria.where("userId").is(history.getUserId())
.and("gameListConfigId").is(history.getGameListConfigId())
);
boolean exists = mongoTemplate.exists(query, UserGamePlayHistory.class);
if (exists) {
mongoTemplate.updateFirst(query,
new Update()
.set("gameInfo", history.getGameInfo())
.set("playTime", history.getPlayTime())
.set("expiredTime", history.getExpiredTime()),
UserGamePlayHistory.class);
} else {
history.setId(String.valueOf(IdWorkerUtils.getId()));
history.setPlayTime(TimestampUtils.now());
history.setExpiredTime(TimestampUtils.nowPlusDays(90));
mongoTemplate.save(history);
}
}
@Override
public List<UserGamePlayHistory> listRecent(String sysOrigin, Long userId, int limit) {
return mongoTemplate.find(
Query.query(Criteria.where("sysOrigin").is(sysOrigin).and("userId").is(userId))
.with(Sort.by(Sort.Order.desc("playTime")))
.limit(limit),
UserGamePlayHistory.class);
}
}