游戏数据统计

This commit is contained in:
2025-03-05 19:18:34 +08:00
parent d0fcde55a4
commit de62c8886e
10 changed files with 375 additions and 20 deletions

View File

@@ -1,20 +1,40 @@
package com.accompany.common.constant; package com.accompany.common.constant;
import com.accompany.common.utils.StringUtils;
public class GameConstant { public class GameConstant {
public enum GameChannel { public enum GameChannel {
LEADERCC( ""), LEADERCC( "", "game_gold_log"),
BAISHUN( "百顺") BAISHUN( "百顺", "bai_shun_game_record"),
; ;
private String desc; private String desc;
private String tableName;
GameChannel(String desc) { GameChannel(String desc, String tableName) {
this.desc = desc; this.desc = desc;
this.tableName = tableName;
} }
public String getDesc() { public String getDesc() {
return desc; return desc;
} }
public String getTableName() {
return tableName;
}
public static GameChannel getByChannel(String channel) {
if (StringUtils.isEmpty(channel)) {
return null;
}
for (GameChannel value : values()) {
if (channel.equals(value.name())) {
return value;
}
}
return null;
}
} }
} }

View File

@@ -13,16 +13,16 @@ public class GameDataTotalVo {
private String statDate; private String statDate;
@ApiModelProperty("新用户参与人数") @ApiModelProperty("新用户参与人数")
private Integer newUserJoinNum; private Integer newUsersCount;
@ApiModelProperty("新用户参与人数") @ApiModelProperty("新用户参与人数")
private Integer totalJoinNum; private Integer totalUsersCount;
@ApiModelProperty("投入") @ApiModelProperty("投入")
private BigDecimal totalBet; private BigDecimal payGold;
@ApiModelProperty("支出") @ApiModelProperty("支出")
private BigDecimal totalWin; private BigDecimal winGold;
@ApiModelProperty("剩余") @ApiModelProperty("剩余")
private BigDecimal totalRemain; private BigDecimal totalRemain;

View File

@@ -206,7 +206,9 @@ public class ShardingSphereConfig {
} }
private AlgorithmConfiguration getGameGoldLogShardingAlgorithmConfiguration() { private AlgorithmConfiguration getGameGoldLogShardingAlgorithmConfiguration() {
return getMonthShardingAlgorithmConfiguration(); AlgorithmConfiguration algorithmConfiguration = getMonthShardingAlgorithmConfiguration();
return algorithmConfiguration;
} }
private AlgorithmConfiguration getBaiShunGameRecordShardingAlgorithmConfiguration() { private AlgorithmConfiguration getBaiShunGameRecordShardingAlgorithmConfiguration() {

View File

@@ -33,10 +33,22 @@ public class GameDayStatData implements Serializable {
* 游戏id * 游戏id
*/ */
private Integer gameId; private Integer gameId;
/**
* 分区id
*/
private Integer partitionId;
/** /**
* 用户uid * 用户uid
*/ */
private Long uid; private Long uid;
/**
* 总参与人数
*/
private Integer totalUsersCount;
/**
* 新用户参与人数
*/
private Integer newUsersCount;
/** /**
* h5小游戏支出 * h5小游戏支出
*/ */

View File

@@ -1,15 +1,46 @@
package com.accompany.business.mybatismapper.game; package com.accompany.business.mybatismapper.game;
import com.accompany.business.model.game.GameDayStatData; import com.accompany.business.model.game.GameDayStatData;
import com.accompany.sharding.vo.GameDataTotalVo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List; import java.util.List;
/** /**
* 游戏日统计表 Mapper 接口 * 游戏日统计表 Mapper 接口
* *
* @author * @author
* @since 2025-03-04 * @since 2025-03-04
*/ */
public interface GameDayStatDataMapper extends BaseMapper<GameDayStatData> { public interface GameDayStatDataMapper extends BaseMapper<GameDayStatData> {
/**
* 统计单天,分区,游戏,用户分组数据(仅限)
*
* @param splitMonth
* @param beginDate
* @param endDate
* @return
*/
List<GameDayStatData> statLeaderccDayList(@Param("splitMonth") String splitMonth, @Param("beginDate") Date beginDate, @Param("endDate") Date endDate);
List<GameDayStatData> statBaiShunDayList(@Param("splitMonth") String splitMonth, @Param("beginDate") Date beginDate, @Param("endDate") Date endDate);
/**
* 统计当天数据(仅限)
*
* @param gameId
* @param beginTime
* @param endTime
* @param partitionId
*/
GameDataTotalVo selectTodayList(@Param("channel") String channel, @Param("tableName") String tableName, @Param("gameId") String gameId,
@Param("beginTime") Date beginTime, @Param("endTime") Date endTime, @Param("partitionId") Integer partitionId);
IPage<GameDataTotalVo> selectGroupDateList(IPage<GameDayStatData> iPage, @Param("channel") String channel, @Param("gameId") String gameId,
@Param("beginTime") Date beginTime, @Param("endTime") Date endTime, @Param("partitionId") Integer partitionId);
} }

View File

@@ -7,6 +7,7 @@ import com.accompany.sharding.vo.GameDataTotalVo;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 游戏日统计表 服务类 * 游戏日统计表 服务类
@@ -18,4 +19,13 @@ public interface GameDayStatDataService extends IService<GameDayStatData> {
PageResult<GameDataTotalVo> totalList( PageResult<GameDataTotalVo> totalList(
String channel, String gameId, String startDate, String endDate, Integer partitionId, Integer pageNo, Integer pageSize); String channel, String gameId, String startDate, String endDate, Integer partitionId, Integer pageNo, Integer pageSize);
/**
*
* @param channel
* @param statTime 传2号统计1号的数据
* @return
*/
void statDayList(String channel, Date statTime);
} }

View File

@@ -1,50 +1,179 @@
package com.accompany.business.service.game.impl; package com.accompany.business.service.game.impl;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import com.accompany.business.model.game.GameDayStatData; import com.accompany.business.model.game.GameDayStatData;
import com.accompany.business.mybatismapper.game.GameDayStatDataMapper; import com.accompany.business.mybatismapper.game.GameDayStatDataMapper;
import com.accompany.business.service.game.GameDayStatDataService; import com.accompany.business.service.game.GameDayStatDataService;
import com.accompany.business.service.game.GameService;
import com.accompany.common.constant.GameConstant; import com.accompany.common.constant.GameConstant;
import com.accompany.common.result.PageResult; import com.accompany.common.result.PageResult;
import com.accompany.core.exception.AdminServiceException; import com.accompany.core.exception.AdminServiceException;
import com.accompany.sharding.vo.GameDataTotalVo; import com.accompany.sharding.vo.GameDataTotalVo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.annotations.Param;
import org.apache.shardingsphere.infra.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.math.RoundingMode;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* 游戏日统计表 服务实现类 * 游戏日统计表 服务实现类
* *
* @author * @author
* @since 2025-03-04 * @since 2025-03-04
*/ */
@Slf4j
@Service @Service
public class GameDayStatDataServiceImpl extends ServiceImpl<GameDayStatDataMapper, GameDayStatData> implements GameDayStatDataService { public class GameDayStatDataServiceImpl extends ServiceImpl<GameDayStatDataMapper, GameDayStatData> implements GameDayStatDataService {
@Autowired
@Qualifier("shardingDataSource")
private DataSource shardingDataSource;
@Autowired
private GameService gameService;
@Override @Override
public PageResult<GameDataTotalVo> totalList( public PageResult<GameDataTotalVo> totalList(
String channel, String gameId, String startDate, String endDate, Integer partitionId, Integer pageNo, Integer pageSize) { String channel, String gameId, String startDate, String endDate, Integer partitionId, Integer pageNo, Integer pageSize) {
if (StringUtils.isEmpty(channel)) { GameConstant.GameChannel byChannel = GameConstant.GameChannel.getByChannel(channel);
if (byChannel == null) {
throw new AdminServiceException("请选择第三方名称"); throw new AdminServiceException("请选择第三方名称");
} }
Date beginTime = null, endTime = null; Date beginTime, endTime;
Date now = new Date();
if (StringUtils.isNotEmpty(startDate) && StringUtils.isNotEmpty(endDate)) { if (StringUtils.isNotEmpty(startDate) && StringUtils.isNotEmpty(endDate)) {
beginTime = DateUtil.parseDateTime(startDate); beginTime = DateUtil.parseDateTime(startDate);
endTime = DateUtil.parseDateTime(endDate); endTime = DateUtil.parseDateTime(endDate);
} else { } else {
endTime = new Date(); endTime = DateUtil.endOfDay(now);
beginTime = DateUtil.offsetDay(endTime, -7); beginTime = DateUtil.offsetDay(endTime, -30);
}
IPage<GameDataTotalVo> gameDayStatDataIPage = baseMapper.selectGroupDateList(new Page<>(pageNo, pageSize), channel, gameId, beginTime, endTime, partitionId);
List<GameDataTotalVo> records = gameDayStatDataIPage.getRecords();
//是否包含今天 今天的单独查
Boolean containsToday = beginTime.before(now) && endTime.after(now);
GameDataTotalVo todayData;
if (containsToday) {
String splitMonth = DateUtil.format(now, DatePattern.SIMPLE_MONTH_PATTERN);
todayData = this.selectDayData(channel, splitMonth, gameId, DateUtil.beginOfDay(now), DateUtil.endOfDay(now), partitionId);
if (todayData != null) {
todayData.setStatDate(DateUtil.formatDate(now));
records.add(0, todayData);
}
}
if (CollectionUtils.isNotEmpty(records)) {
for (GameDataTotalVo record : records) {
record.setTotalRemain(record.getPayGold().subtract(record.getWinGold()));
record.setBetRate(record.getTotalRemain().divide(record.getPayGold(), 4, RoundingMode.DOWN));
}
}
return new PageResult<>(gameDayStatDataIPage);
}
@Override
public void statDayList(String channel, Date statTime) {
log.info("GameDayStatDataServiceImpl.statDayList-begin channel:{}, statTime:{}", channel, statTime);
Date endDate = DateUtil.beginOfDay(statTime);
Date beginDate = DateUtil.offsetDay(endDate, -1);
String splitMonth = DateUtil.format(beginDate, DatePattern.SIMPLE_MONTH_PATTERN);
List<GameDayStatData> gameDayStatDatas = null;
if (GameConstant.GameChannel.LEADERCC.name().equals(channel)) {
gameDayStatDatas = baseMapper.statLeaderccDayList(splitMonth, beginDate, endDate);
} else if (GameConstant.GameChannel.BAISHUN.name().equals(channel)) {
gameDayStatDatas = baseMapper.statBaiShunDayList(splitMonth, beginDate, endDate);
}
if (CollectionUtils.isEmpty(gameDayStatDatas)) {
return;
}
for (GameDayStatData gameDayStatData : gameDayStatDatas) {
gameDayStatData.setStatDate(beginDate);
gameDayStatData.setCreateTime(statTime);
}
baseMapper.insert(gameDayStatDatas);
log.info("GameDayStatDataServiceImpl.statDayList-end channel:{}, statTime:{}", channel, statTime);
}
private GameDataTotalVo selectDayData(String channel, String splitMonth, String gameId, Date beginTime, Date endTime, Integer partitionId) {
StringBuilder querySql = new StringBuilder(" SELECT\n" +
" DATE(g.create_time) AS statDate,\n" +
" COUNT(DISTINCT u.uid) AS totalUsersCount,\n" +
" COUNT(\n" +
" DISTINCT\n" +
" CASE\n" +
" WHEN u.create_time >= DATE(g.create_time) - INTERVAL 1 DAY\n" +
" AND u.create_time < DATE(g.create_time) THEN g.uid END) AS newUsersCount,");
if(GameConstant.GameChannel.LEADERCC.name().equals(channel)){
querySql.append(" IFNULL(sum(if(g.`type` = 1, g.`coin`, '0')), 0) payGold,\n" +
" IFNULL(sum(if(g.`type` = 2, g.`coin`, '0')), 0) winGold\n" +
" FROM game_gold_log g");
} else {
querySql.append(" ifnull(abs(sum(g.if(currency_diff < 0, g.`currency_diff`, '0'))), 0) payGold,\n" +
" ifnull(sum(if(g.currency_diff > 0, g.`currency_diff`, '0')), 0) winGold\n" +
" FROM bai_shun_game_record g");
}
String tableName = GameConstant.GameChannel.getByChannel(channel).getTableName();
querySql.append(" WHERE\n" +
" g.create_time >= ?\n" +
" AND g.create_time < ?");
List<String> params = new ArrayList<>();
params.add(DateUtil.formatDateTime(beginTime));
params.add(DateUtil.formatDateTime(endTime));
if (StringUtils.isNotEmpty(gameId) && !gameService.ALL.equals(gameId)) {
querySql.append(" AND g.game_id = ?");
params.add(gameId);
}
if (partitionId != null) {
querySql.append(" AND u.partition_id = ?");
params.add(partitionId.toString());
} }
if (GameConstant.GameChannel.LEADERCC.name().equals(channel)) { try (HintManager hintManager = HintManager.getInstance();
return null; Connection conn = shardingDataSource.getConnection();
} else if (GameConstant.GameChannel.BAISHUN.name().equals(channel)) { PreparedStatement preparedStatement = conn.prepareStatement(querySql.toString())) {
return null; hintManager.addTableShardingValue(tableName, splitMonth);
int i = 1;
for (String param : params) {
preparedStatement.setString(i++, param);
}
try (ResultSet rs = preparedStatement.executeQuery()) {
List<GameDataTotalVo> result = new ArrayList<>();
while (rs.next()) {
GameDataTotalVo vo = new GameDataTotalVo();
vo.setStatDate(rs.getString("statDate"));
vo.setTotalUsersCount(rs.getInt("totalUsersCount"));
vo.setNewUsersCount(rs.getInt("newUsersCount"));
vo.setPayGold(rs.getBigDecimal("payGold"));
vo.setWinGold(rs.getBigDecimal("winGold"));
result.add(vo);
}
if (CollectionUtils.isNotEmpty(result)) {
return result.get(0);
}
return null;
}
} catch (SQLException e) {
e.printStackTrace();
} }
return null; return null;
} }
} }

View File

@@ -2,4 +2,121 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.accompany.business.mybatismapper.game.GameDayStatDataMapper"> <mapper namespace="com.accompany.business.mybatismapper.game.GameDayStatDataMapper">
<select id="statLeaderccDayList" resultType="com.accompany.business.model.game.GameDayStatData">
SELECT
DATE(g.create_time) AS statDate,
g.game_id gameId,
u.partition_id partitionId,
g.uid uid,
COUNT(DISTINCT u.uid) AS totalUsersCount,
COUNT(
DISTINCT
CASE
WHEN u.create_time >= DATE(g.create_time) - INTERVAL 1 DAY
AND u.create_time &lt; DATE(g.create_time) THEN
u.uid
END) AS newUsersCount,
sum(if(g.`type` = 1, g.`coin`, '0')) payGold,
sum(if(g.`type` = 2, g.`coin`, '0')) winGold
FROM
`game_gold_log_${splitMonth}` g
LEFT JOIN users u ON g.uid = u.uid
WHERE
g.create_time >= #{beginDate}
AND g.create_time &lt; #{endDate}
GROUP BY
statDate,
u.partition_id,
g.game_id,
g.uid
</select>
<select id="statBaiShunDayList" resultType="com.accompany.business.model.game.GameDayStatData">
SELECT
DATE(g.create_time) AS statDate,
g.game_id gameId,
u.partition_id partitionId,
g.uid uid,
COUNT(DISTINCT u.uid) AS totalUsersCount,
COUNT(
DISTINCT
CASE
WHEN u.create_time >= DATE(g.create_time) - INTERVAL 1 DAY
AND u.create_time &lt; DATE(g.create_time) THEN
u.uid
END) AS newUsersCount,
abs(sum(g.if(currency_diff &lt; 0, g.`currency_diff`, '0'))) payGold,
sum(if(g.currency_diff > 0, g.`currency_diff`, '0')) winGold
FROM
bai_shun_game_record_${splitMonth} g
LEFT JOIN users u ON g.uid = u.uid
WHERE
g.create_time >= #{beginDate}
AND g.create_time &lt; #{endDate}
GROUP BY
statDate,
u.partition_id,
g.game_id,
g.uid
</select>
<sql id="dynamicTable">
<choose>
<when test="channel == 'LEADERCC'">
IFNULL(sum(if(g.`type` = 1, g.`coin`, '0')), 0) payGold,
IFNULL(sum(if(g.`type` = 2, g.`coin`, '0')), 0) winGold
FROM game_gold_log g
</when>
<when test="channel == 'BAISHUN'">
ifnull(abs(sum(g.if(currency_diff &lt; 0, g.`currency_diff`, '0'))), 0) payGold,
ifnull(sum(if(g.currency_diff > 0, g.`currency_diff`, '0')), 0) winGold
FROM bai_shun_game_record g
</when>
</choose>
</sql>
<select id="selectTodayList" resultType="com.accompany.sharding.vo.GameDataTotalVo">
SELECT
DATE(g.create_time) AS statDate,
COUNT(DISTINCT u.uid) AS totalUsersCount,
COUNT(
DISTINCT
CASE
WHEN u.create_time >= DATE(g.create_time) - INTERVAL 1 DAY
AND u.create_time &lt; DATE(g.create_time) THEN g.uid END) AS newUsersCount,
<include refid="dynamicTable"/>
WHERE
g.create_time >= #{beginTime}
AND g.create_time &lt; #{endTime}
<if test="gameId != null and gameId !='' and 'ALL' != gameId">
AND g.game_id = #{gameId}
</if>
<if test="partitionId != null and partitionId != 0">
AND u.partition_id = #{partitionId}
</if>
</select>
<select id="selectGroupDateList" resultType="com.accompany.sharding.vo.GameDataTotalVo">
SELECT
stat_date statDate,
`channel` channel,
ifnull(sum(total_users_count),0) totalUsersCount,
ifnull(sum(new_users_count),0) newUsersCount,
ifnull(sum(pay_gold),0) payGold,
ifnull(sum(win_gold),0) winGold
from game_day_stat_data
where `channel` = #{channel}
<if test="beginTime != null">
and stat_date >= #{beginTime}
</if>
<if test="endTime != null">
and stat_date &lt;= #{endTime}
</if>
<if test="gameId != null and gameId !='' and 'ALL' != gameId">
and game_id = #{gameId}
</if>
<if test="partitionId != null and partitionId != 0">
and partition_id = #{partitionId}
</if>
</select>
</mapper> </mapper>

View File

@@ -1,7 +1,9 @@
package com.accompany.business.controller.game; package com.accompany.business.controller.game;
import cn.hutool.core.date.DateUtil;
import com.accompany.business.config.LeaderccMiniGameConfig; import com.accompany.business.config.LeaderccMiniGameConfig;
import com.accompany.business.param.neteasepush.MD5Utils; import com.accompany.business.param.neteasepush.MD5Utils;
import com.accompany.business.service.game.GameDayStatDataService;
import com.accompany.business.service.game.GameService; import com.accompany.business.service.game.GameService;
import com.accompany.business.vo.game.GameConsumeRequestVO; import com.accompany.business.vo.game.GameConsumeRequestVO;
import com.accompany.business.vo.game.GameResponseVO; import com.accompany.business.vo.game.GameResponseVO;
@@ -18,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@Slf4j @Slf4j
@RestController @RestController
@RequestMapping("/game") @RequestMapping("/game")
@@ -27,6 +31,8 @@ public class GameController {
private GameService gameService; private GameService gameService;
@Autowired @Autowired
private LeaderccMiniGameConfig leaderccMiniGameConfig; private LeaderccMiniGameConfig leaderccMiniGameConfig;
@Autowired
private GameDayStatDataService gameDayStatDataService;
@PostMapping("/gold") @PostMapping("/gold")
public GameResponseVO<JSONObject> changeGold(@RequestBody @Validated GameConsumeRequestVO param){ public GameResponseVO<JSONObject> changeGold(@RequestBody @Validated GameConsumeRequestVO param){
@@ -76,6 +82,11 @@ public class GameController {
return gameService.changeGold(param); return gameService.changeGold(param);
} }
@PostMapping("/statData")
public void statData(String channel, String statTime) {
gameDayStatDataService.statDayList(channel, DateUtil.parseDateTime(statTime));
}
public static void main(String[] args) { public static void main(String[] args) {
String token = "b7b68181-4005-441b-affb-3b20cd75519e"; String token = "b7b68181-4005-441b-affb-3b20cd75519e";
String gameId = "1"; String gameId = "1";

View File

@@ -0,0 +1,23 @@
package com.accompany.scheduler.task.game;
import com.accompany.business.service.game.GameDayStatDataService;
import com.accompany.common.constant.GameConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class GameDayStatDataTask {
@Autowired
private GameDayStatDataService gameDayStatDataService;
@Scheduled(cron = "0 10 0 * * *")
public void statDayList() {
for (GameConstant.GameChannel gameChannel : GameConstant.GameChannel.values()) {
gameDayStatDataService.statDayList(gameChannel.name(), new Date());
}
}
}