ComplexSummaryGameServer.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. <?php
  2. namespace app\crontab;
  3. use app\service\ComplexSummaryService;
  4. use app\service\RetaineService;
  5. use think\console\Command;
  6. use think\console\Input;
  7. use think\console\Output;
  8. use think\console\Input\Argument;
  9. use think\Db;
  10. use think\Exception;
  11. use think\Log;
  12. /**
  13. * 聚合-游戏区服数据-统计脚本
  14. * 统计游戏区服数据汇总
  15. */
  16. class ComplexSummaryGameServer extends Command
  17. {
  18. protected function configure()
  19. {
  20. $this->setName('ComplexSummaryGameServer')
  21. ->setDescription('聚合-游戏区服数据-统计脚本')
  22. ->addArgument('day', Argument::OPTIONAL, '统计日期,格式:Y-m-d,默认为昨天');
  23. }
  24. protected function execute(Input $input, Output $output)
  25. {
  26. $service = new ComplexSummaryService();
  27. // 获取统计日期(默认昨天)
  28. $day = $input->getArgument('day') ?: date('Y-m-d', strtotime('-1 day'));
  29. $output->writeln(date('Y-m-d H:i:s') . " 开始统计日期: {$day}");
  30. try {
  31. // 1. 获取玩家总数(累计到当天之前)
  32. $memberTotalList = $this->getMemberTotalByServer($day, $service);
  33. $output->writeln("玩家总数统计完成: " . count($memberTotalList) . " 条");
  34. // 2. 获取新增玩家(按区服分组)
  35. $regNumList = $this->getRegNumByServer($day, $service);
  36. $output->writeln("新增玩家统计完成: " . count($regNumList) . " 条");
  37. // 3. 获取新增角色(按区服分组)
  38. $regRoleNumList = $service->getRegRoleNum($day, [], 1, 1);
  39. $output->writeln("新增角色统计完成: " . count($regRoleNumList) . " 条");
  40. // 4. 获取活跃玩家(按区服分组)
  41. $activeUserList = $service->getActiveUserByActiveLog($day, [], 1, 1);
  42. $output->writeln("活跃玩家统计完成: " . count($activeUserList) . " 条");
  43. // 5. 获取 订单金额+实付金额(按区服分组)
  44. $payMoneyList = $this->getPayMoneyByServer($day, $service);
  45. $output->writeln("充值金额统计完成: " . count($payMoneyList) . " 条");
  46. // 6. 获取付费人数(按区服分组)
  47. $payUserNumList = $this->getPayUserNumByServer($day, $service);
  48. $output->writeln("付费人数统计完成: " . count($payUserNumList) . " 条");
  49. // 7. 合并所有统计数据
  50. $allStats = $service->mergeStatsData([
  51. $memberTotalList,
  52. $regNumList,
  53. $regRoleNumList,
  54. $activeUserList,
  55. $payMoneyList,
  56. $payUserNumList
  57. ]);
  58. // dump(json_encode($allStats));
  59. // 8. 计算付费率、ARPU和ARPPU,并保存数据
  60. // 先清空当天日期的数据,避免重新抓取时数据错乱(在事务外执行,确保清空操作生效)
  61. Db::table('cy_complex_summary_game_server')
  62. ->where('day', $day)
  63. ->delete();
  64. Db::startTrans();
  65. try {
  66. foreach ($allStats as $stat) {
  67. $gameId = $stat['game_id'] ?? 0;
  68. $complexId = $stat['channel_id'] ?? 0;
  69. $serverId = $stat['server_id'] ?? 0;
  70. $serverName = $stat['server_name'] ?? '';
  71. // 过滤掉 game_id 或 server_id 为 0 或 NULL 的无效记录
  72. if (empty($gameId) || empty($serverId)) {
  73. continue;
  74. }
  75. // 计算付费率、ARPU和ARPPU
  76. $payMoney = floatval($stat['pay_money'] ?? 0);
  77. $payAmount = floatval($stat['pay_amount'] ?? 0);
  78. $activeUserNum = intval($stat['active_user_num'] ?? 0);
  79. $payUserNum = intval($stat['pay_user_num'] ?? 0);
  80. $payRate = $activeUserNum > 0 ? round(100 * $payUserNum / $activeUserNum, 2)*100 : 0;
  81. $arpu = $activeUserNum > 0 ? round($payMoney / $activeUserNum, 2)*100 : 0;
  82. $arppu = $payUserNum > 0 ? round($payMoney / $payUserNum, 2)*100 : 0;
  83. // 准备保存的数据
  84. $saveData = [
  85. 'day' => $day,
  86. 'game_id' => $gameId,
  87. 'complex_id' => $complexId,
  88. 'server_id' => $serverId,
  89. 'server_name' => $serverName,
  90. 'reg_total' => intval($stat['reg_total'] ?? 0),
  91. 'reg_num' => intval($stat['reg_num'] ?? 0),
  92. 'reg_role_num' => intval($stat['reg_role_num'] ?? 0),
  93. 'active_user_num' => $activeUserNum,
  94. 'pay_money' => $payMoney,
  95. 'pay_amount' => $payAmount,
  96. 'pay_user_num' => $payUserNum,
  97. 'pay_rate_num' => $payRate,
  98. 'arpu' => $arpu,
  99. 'arppu' => $arppu,
  100. ];
  101. // Log::write("## ComplexSummaryGameServer: ".json_encode($saveData),'notice');
  102. // 直接插入(已清空当天数据,无需检查是否存在)
  103. Db::table('cy_complex_summary_game_server')->insert($saveData);
  104. // 以下代码已注释,防止以后需要时可以恢复
  105. // // 检查记录是否存在
  106. // $exists = Db::table('cy_complex_summary_game_server')
  107. // ->where([
  108. // 'day' => $day,
  109. // 'game_id' => $gameId,
  110. // 'complex_id' => $complexId,
  111. // 'server_id' => $serverId
  112. // ])
  113. // ->find();
  114. //
  115. // if ($exists) {
  116. // // 更新
  117. // Db::table('cy_complex_summary_game_server')
  118. // ->where(['id' => $exists['id']])
  119. // ->update($saveData);
  120. // } else {
  121. // // 插入
  122. // Db::table('cy_complex_summary_game_server')->insert($saveData);
  123. // }
  124. }
  125. Db::commit();
  126. $output->writeln("数据保存成功,共处理 " . count($allStats) . " 条记录");
  127. } catch (Exception $e) {
  128. Db::rollback();
  129. $output->writeln("保存数据失败: " . $e->getMessage());
  130. throw $e;
  131. }
  132. } catch (Exception $e) {
  133. $output->writeln("统计失败: " . $e->getMessage() . ' - ' . $e->getFile() . ":" . $e->getLine());
  134. throw $e;
  135. }
  136. $output->writeln(date('Y-m-d H:i:s') . " 统计完成");
  137. }
  138. /**
  139. * 获取玩家总数(累计到指定日期之前)
  140. */
  141. private function getMemberTotalByServer($day, $service)
  142. {
  143. $gameIds = $service->getOnlineGameIds();
  144. if (empty($gameIds)) {
  145. return [];
  146. }
  147. $endTime = strtotime($day . ' 23:59:59');
  148. // 直接使用角色表统计累计玩家总数(按区服分组)
  149. $result = Db::table('nw_complex_role')
  150. ->where('create_time', '<', $endTime)
  151. ->where('game_id', 'in', $gameIds)
  152. ->whereNotNull('server_id') // 过滤掉 server_id 为 NULL 的记录
  153. ->field('game_id, complex_id as channel_id, server_id,
  154. COUNT(DISTINCT member_id) as reg_total')
  155. ->group('game_id, complex_id, server_id')
  156. // ->fetchSql(true)
  157. ->select();
  158. // dump($result);
  159. // 转换为数组
  160. $result = $result ?: [];
  161. if (!is_array($result) && method_exists($result, 'toArray')) {
  162. $result = $result->toArray();
  163. }
  164. if (empty($result)) {
  165. return [];
  166. }
  167. // 收集所有的 server_id
  168. $serverIds = array_filter(array_unique(array_column($result, 'server_id')));
  169. // 批量查询区服名称
  170. $serverNames = [];
  171. if (!empty($serverIds)) {
  172. $serverList = Db::table('nw_complex_server')
  173. ->where('id', 'in', $serverIds)
  174. ->column('server_name', 'id');
  175. $serverNames = $serverList ?: [];
  176. }
  177. // 合并区服名称到结果中
  178. foreach ($result as &$item) {
  179. $item['server_name'] = $serverNames[$item['server_id']] ?? '';
  180. }
  181. unset($item);
  182. return $result;
  183. }
  184. /**
  185. * 获取新增玩家(按区服分组)
  186. */
  187. private function getRegNumByServer($day, $service)
  188. {
  189. $timeRange = $service->buildTimeRange($day);
  190. list($startTime, $endTime) = $timeRange;
  191. $gameIds = $service->getOnlineGameIds();
  192. if (empty($gameIds)) {
  193. return [];
  194. }
  195. $result = Db::table('nw_complex_role')
  196. ->whereBetween('create_time', [$startTime, $endTime])
  197. ->where('game_id', 'in', $gameIds)
  198. // ->whereNotNull('r.server_id') // 过滤掉没有角色信息的记录,避免 server_id 为 NULL
  199. ->field('game_id, complex_id as channel_id, server_id, COUNT(DISTINCT member_id) as reg_num')
  200. ->group('game_id, complex_id, server_id')
  201. ->select();
  202. // 转换为数组
  203. $result = $result ?: [];
  204. if (!is_array($result) && method_exists($result, 'toArray')) {
  205. $result = $result->toArray();
  206. }
  207. if (empty($result)) {
  208. return [];
  209. }
  210. // 收集所有的 server_id
  211. $serverIds = array_filter(array_unique(array_column($result, 'server_id')));
  212. // 批量查询区服名称
  213. $serverNames = [];
  214. if (!empty($serverIds)) {
  215. $serverList = Db::table('nw_complex_server')
  216. ->where('id', 'in', $serverIds)
  217. ->column('server_name', 'id');
  218. $serverNames = $serverList ?: [];
  219. }
  220. // 合并区服名称到结果中
  221. foreach ($result as &$item) {
  222. $item['server_name'] = $serverNames[$item['server_id']] ?? '';
  223. }
  224. unset($item);
  225. // dump($result);
  226. return $result;
  227. }
  228. /**
  229. * 获取充值金额(按区服分组)
  230. */
  231. private function getPayMoneyByServer($day, $service)
  232. {
  233. $timeRange = $service->buildTimeRange($day);
  234. list($startTime, $endTime) = $timeRange;
  235. $gameIds = $service->getOnlineGameIds();
  236. if (empty($gameIds)) {
  237. return [];
  238. }
  239. // 根据订单的渠道、游戏和业务区服ID关联区服表,汇总表统一保存区服表内部ID。
  240. // 区服表可能存在相同业务区服的重复记录,这里固定取最小ID,避免关联后放大充值金额。
  241. $result = Db::table('nw_complex_pay')->alias('p')
  242. ->join(
  243. 'nw_complex_server s',
  244. 's.id = (
  245. SELECT MIN(s2.id)
  246. FROM nw_complex_server s2
  247. WHERE s2.complex_id = p.channel_id
  248. AND s2.game_id = p.gameid
  249. AND s2.server_id = p.serverid
  250. )',
  251. 'inner'
  252. )
  253. ->whereBetween('p.create_time', [$startTime, $endTime])
  254. ->where('p.status', 1)
  255. ->where('p.gameid', 'in', $gameIds)
  256. ->whereNotNull('p.gameid')
  257. ->whereNotNull('p.serverid')
  258. ->field('s.game_id, s.complex_id as channel_id, s.id as server_id,
  259. SUM(p.amount) as pay_money,
  260. SUM(p.pay_amount) as pay_amount,
  261. s.server_name')
  262. // COUNT(DISTINCT p.userid) as pay_user_num,
  263. ->group('s.game_id, s.complex_id, s.id, s.server_name')
  264. ->select();
  265. return $result ?: [];
  266. }
  267. /**
  268. * 获取付费人数(按区服分组)
  269. */
  270. private function getPayUserNumByServer($day, $service)
  271. {
  272. $timeRange = $service->buildTimeRange($day);
  273. list($startTime, $endTime) = $timeRange;
  274. $gameIds = $service->getOnlineGameIds();
  275. if (empty($gameIds)) {
  276. return [];
  277. }
  278. // 先获取当天有充值的用户ID
  279. $payUserIds = Db::table('nw_complex_pay')
  280. ->whereBetween('create_time', [$startTime, $endTime])
  281. ->where('status', 1)
  282. ->column('DISTINCT userid');
  283. if (empty($payUserIds)) {
  284. return [];
  285. }
  286. // 通过用户表和角色表关联获取区服信息
  287. $result = Db::table('nw_complex_members')
  288. ->alias('m')
  289. ->join('nw_complex_role r', 'm.id = r.member_id', 'left')
  290. ->where('m.id', 'in', $payUserIds)
  291. ->where('m.total_pay_amount', '>', 0)
  292. ->where('r.game_id', 'in', $gameIds)
  293. ->whereNotNull('r.game_id') // 明确过滤掉没有角色信息的记录
  294. ->whereNotNull('r.server_id') // 过滤掉没有区服信息的记录,避免 server_id 为 NULL
  295. ->field('r.game_id, r.complex_id as channel_id, r.server_id,
  296. COUNT(DISTINCT m.id) as pay_user_num,
  297. (SELECT server_name FROM nw_complex_server WHERE id = r.server_id LIMIT 1) as server_name')
  298. ->group('r.game_id, r.complex_id, r.server_id')
  299. ->select();
  300. return $result ?: [];
  301. }
  302. }