JhApi.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. <?php
  2. namespace app\api\controller;
  3. use Exception;
  4. use think\Cache;
  5. use think\Controller;
  6. use think\Db;
  7. use think\Env;
  8. use think\Request;
  9. /**
  10. * 聚合平台对接
  11. */
  12. class JhApi extends Controller
  13. {
  14. protected $api_sign_key;
  15. public function __construct(Request $request = null)
  16. {
  17. parent::__construct($request);
  18. $this->api_sign_key = Env::get("qmjh.api_sign_key");
  19. log_message('__construct.input:' . json_encode(input()), 'log', LOG_PATH . 'jh_log/');
  20. }
  21. /**
  22. * sign
  23. *
  24. * @param $data
  25. *
  26. * @return string
  27. */
  28. private function sign($data)
  29. {
  30. unset($data['sign']);
  31. $data['key'] = $this->api_sign_key;
  32. ksort($data);
  33. $string = '';
  34. foreach ($data as $k => $v) {
  35. if ($string) {
  36. $string .= '&' . $k . "=" . $v;
  37. } else {
  38. $string = $k . "=" . $v;
  39. }
  40. }
  41. return md5($string);
  42. }
  43. public function getRole()
  44. {
  45. $data = input();
  46. $type = input('type', 0); // 类型:0=总数;1=列表
  47. $params = input('params');
  48. if (!isset($type) || !isset($params)) {
  49. return json(["code" => -100, "msg" => '缺少必要参数!', "data" => []]);
  50. }
  51. $signRes = $this->sign($data);
  52. // dump($signRes);
  53. if ($signRes != $data['sign']) {
  54. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  55. }
  56. if (!is_json($params)) {
  57. return json(["code" => -110, "msg" => '查询数据格式有误!', "data" => []]);
  58. }
  59. $listBy = json_decode($params, true);
  60. if(count($listBy) > 2000){
  61. return json(["code" => -120, "msg" => '查询数据条数限制超 2000条', "data" => []]);
  62. }
  63. try {
  64. // 数据标识处理
  65. $markByList = [];
  66. array_walk($listBy, function ($v) use (&$markByList) {
  67. // $markByList[$v['id']] = $v['aid'] . '_' . $v['gid'].'_'.$v['id'];
  68. $markByList[$v['id']] = $v['aid'] . '_' . $v['gid'];
  69. });
  70. $markByFlip = array_flip($markByList);
  71. $modelHandle = Db::table('nw_member_game_server');
  72. $modelHandle->where(function ($query) use ($listBy) {
  73. foreach ($listBy as $v) {
  74. $query->whereOr(function ($query) use ($v) {
  75. $query->where(['member_id' => $v['aid'], 'game_id' => $v['gid']]);
  76. });
  77. }
  78. });
  79. $result = [];
  80. if ($type == 1) {
  81. $list = $modelHandle->field("mgs_id,roleid,rolename,serverid,servername,FROM_UNIXTIME(create_time, '%Y-%m-%d %H:%i:%s') as create_time,CONCAT(`member_id`,'_',`game_id`) as mark")
  82. ->chunk(500, function($items) use ($markByFlip, &$result) {
  83. foreach ($items as $item) {
  84. if (empty($markByFlip[$item['mark']])) {
  85. break;
  86. }
  87. $key = $markByFlip[$item['mark']];
  88. $result[$key][] = ['role_id' => $item['roleid'], 'rolename' => $item['rolename'], 'server_id' => $item['serverid'], 'server_name' => $item['servername'], 'create_time' => $item['create_time']];
  89. }
  90. });
  91. } else {
  92. $list = $modelHandle->field("CONCAT(`member_id`,'_',`game_id`) as mark, count(*) as count")->group('mark')->select();
  93. foreach ($list as $v) {
  94. if (empty($markByFlip[$v['mark']])) {
  95. break;
  96. }
  97. $key = $markByFlip[$v['mark']];
  98. $result[$key][] = $v['count'];
  99. }
  100. }
  101. if (!$list) {
  102. return json(["code" => 200, "msg" => '没有相关数据!', "data" => []]);
  103. }
  104. } catch (\Exception $e) {
  105. // dump($e->getMessage(), $e->getFile() . ' : ' . $e->getLine());
  106. return json(["code" => -120, "msg" => '程序异常:' . $e->getMessage() . ' - ' . $e->getFile() . ' : ' . $e->getLine(), "data" => []]);
  107. }
  108. return json(["code" => 200, "msg" => '成功', "data" => $result]);
  109. }
  110. /**
  111. * 获取游戏账户列表
  112. *
  113. * @return \think\response\Json
  114. * @throws \think\db\exception\DataNotFoundException
  115. * @throws \think\db\exception\ModelNotFoundException
  116. * @throws \think\exception\DbException
  117. */
  118. public function getSubList()
  119. {
  120. $input = input();
  121. $signRes = $this->sign($input);
  122. if ($signRes != $input['sign']) {
  123. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  124. }
  125. $date = $input['date'] ?? '';
  126. if ($date) {
  127. $where['cl.login_time'] = ['between', [strtotime($date . ' 00:00:00'), strtotime($date . ' 23:59:59')]];
  128. } else {
  129. // 三个月时间
  130. $where['cl.login_time'] = ['between', [strtotime('-3 months'), strtotime($date . ' 23:59:59')]];
  131. }
  132. if (!empty($input['game_id'])) {
  133. $where['cl.gameid'] = ['in', explode(',', $input['game_id'])];
  134. }
  135. $list = Db::table('cy_logininfo')->alias('cl')
  136. ->join('cy_members cm', 'cl.userid=cm.id', 'left')
  137. ->join('cy_game cg', 'cl.gameid=cg.id', 'left')
  138. ->join('nw_channel nc', 'cl.channel_id=nc.id', 'left')
  139. ->where(['nc.gh_status' => 1])->where($where)
  140. ->field('cm.username as user_name,cl.gameid as game_id,max(cl.login_time) as update_time')
  141. ->group('cl.gameid,cl.sub_id')
  142. ->page($input['page'] ?? 1, $input['limit'] ?? 100)
  143. ->select();
  144. $total = Db::table('cy_logininfo')->alias('cl')
  145. ->join('cy_members cm', 'cl.userid=cm.id', 'left')
  146. ->join('cy_game cg', 'cl.gameid=cg.id', 'left')
  147. ->join('nw_channel nc', 'cl.channel_id=nc.id', 'left')
  148. ->field('cm.username as user_name,cl.gameid as game_id,max(cl.login_time) as update_time')
  149. ->where(['nc.gh_status' => 1])->where($where)
  150. ->group('cl.gameid,cl.sub_id')
  151. ->count();
  152. return json(["code" => 200, "msg" => 'success', "data" => $list, 'count' => $total]);
  153. }
  154. /**
  155. * 获取渠道数据汇总
  156. *
  157. * @return \think\response\Json
  158. * @throws \think\db\exception\DataNotFoundException
  159. * @throws \think\db\exception\ModelNotFoundException
  160. * @throws \think\exception\DbException
  161. */
  162. public function getChannelDataSummary()
  163. {
  164. $input = input();
  165. $signRes = $this->sign($input);
  166. if ($signRes != $input['sign']) {
  167. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  168. }
  169. $date = $input['date'] ?? '';
  170. if (!$date) {
  171. return json(["code" => -120, "msg" => '参数错误,缺少date参数!', "data" => []]);
  172. }
  173. $where = [
  174. 'crg.day' => $date,
  175. 'crg.type_id' => 2,
  176. 'crg.channel_id' => ['>', 0],
  177. ];
  178. $list = Db::table('cy_retaine_game')->alias('crg')
  179. ->join('cy_game cg', 'cg.id=crg.game_id AND cg.cooperation_status=1', 'left')
  180. ->join('nw_channel nc', 'nc.id=crg.channel_id AND nc.gh_status=1', 'left')
  181. ->field("crg.type_id, crg.game_id, cg.name as game_name, crg.channel_id, nc.name as channel_name, crg.reg_num, crg.role_num, crg.act_num, crg.recharge_num, crg.pay_num")
  182. ->where($where)
  183. ->where(function ($query) {
  184. // 非空条件:至少有一个字段不为空
  185. $query->whereOr('crg.reg_num', '>', 0)
  186. ->whereOr('crg.role_num', '>', 0)
  187. ->whereOr('crg.act_num', '>', 0)
  188. ->whereOr('crg.recharge_num', '>', 0)
  189. ->whereOr('crg.pay_num', '>', 0);
  190. })
  191. ->order("crg.day desc, crg.reg_num desc, crg.game_id desc")
  192. ->select();
  193. return json(["code" => 200, "msg" => 'success', "data" => $list]);
  194. }
  195. public function getChannelDataSummaryV2()
  196. {
  197. $input = input();
  198. $signRes = $this->sign($input);
  199. if (empty($input['sign']) || $signRes != $input['sign']) {
  200. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  201. }
  202. if (empty($input['date'])) {
  203. return json(["code" => -120, "msg" => '参数错误,缺少date参数!', "data" => []]);
  204. }
  205. $type = input('type', 1); // 类型: 1=汇总, 2=详情
  206. $where = [
  207. 'crg.day' => $input['date'],
  208. 'crg.type_id' => 2,
  209. 'crg.game_id' => ['>', 0],
  210. 'crg.channel_id' => ['>', 0],
  211. ];
  212. // , crg.old_act_num, crg.old_pay_num, crg.old_recharge_num
  213. $filed = "crg.id, crg.day, crg.game_id, cg.name as game_name, crg.channel_id, nc.name as channel_name, crg.reg_num, crg.role_num, crg.act_num, crg.recharge_num, crg.pay_num, crg.new_act_num, crg.new_pay_num, crg.new_recharge_num";
  214. if ($type == 2) {
  215. $filed = "crg.id, crg.day, crg.game_id, cg.name as game_name, crg.channel_id, nc.name as channel_name, crg.reg_total, crg.reg_num, crg.one_stay, crg.three_stay, crg.four_stay, crg.five_stay, crg.six_stay, crg.seven_stay, crg.fifteen_stay, crg.thirty_stay";
  216. }
  217. $listRows = intval(input('list_rows', 50));
  218. $pageNum = intval(input('page', 1));
  219. $retaineList = Db::table('cy_retaine_game')->alias('crg')
  220. ->join('cy_game cg', 'cg.id=crg.game_id AND cg.cooperation_status=1', 'left')
  221. ->join('nw_channel nc', 'nc.id=crg.channel_id AND nc.gh_status=1')
  222. ->field($filed)
  223. ->where($where)
  224. ->where(function ($query) use ($type) {
  225. // 非空条件:至少有一个字段不为空
  226. if ($type == 1) {
  227. $query->whereOr('crg.reg_num', '>', 0)
  228. ->whereOr('crg.role_num', '>', 0)
  229. ->whereOr('crg.act_num', '>', 0)
  230. ->whereOr('crg.recharge_num', '>', 0)
  231. ->whereOr('crg.pay_num', '>', 0)
  232. ->whereOr('crg.new_act_num', '>', 0)
  233. ->whereOr('crg.new_pay_num', '>', 0)
  234. ->whereOr('crg.new_recharge_num', '>', 0);
  235. } else {
  236. $query->whereOr('crg.reg_num', '>', 0)
  237. ->whereOr('crg.one_stay', '>', 0)
  238. ->whereOr('crg.three_stay', '>', 0)
  239. ->whereOr('crg.four_stay', '>', 0)
  240. ->whereOr('crg.five_stay', '>', 0)
  241. ->whereOr('crg.six_stay', '>', 0)
  242. ->whereOr('crg.seven_stay', '>', 0)
  243. ->whereOr('crg.fifteen_stay', '>', 0)
  244. ->whereOr('crg.thirty_stay', '>', 0);
  245. }
  246. })
  247. ->order("crg.id asc")
  248. ->paginate($listRows, false, ['page' => $pageNum]);
  249. $pageArr = $retaineList->toArray(); // 包含 data, total, per_page, current_page, last_page 等
  250. if ($type == 1) {
  251. $list = $pageArr['data'] ?? [];
  252. foreach ($list as &$v) {
  253. $old_recharge_num = $v['recharge_num'] - $v['new_recharge_num'];
  254. if ($old_recharge_num <= 0) {
  255. $old_recharge_num = "0.00";
  256. }
  257. $v['old_act_num'] = $v['old_pay_num'] = $v['old_recharge_num'] = 0;
  258. if($v['act_num'] > 0 && ($v['act_num'] - $v['new_act_num']) > 0){
  259. $v['old_act_num'] = $v['act_num'] - $v['new_act_num'];
  260. }
  261. if($v['pay_num'] > 0 && ($v['pay_num'] - $v['new_pay_num']) > 0){
  262. $v['old_pay_num'] = $v['pay_num'] - $v['new_pay_num'];
  263. }
  264. $v['old_recharge_num'] = sprintf("%.2f", $old_recharge_num);
  265. }
  266. $pageArr['data'] = $list;
  267. }
  268. return json(["code" => 200, "msg" => 'success', "data" => $pageArr]);
  269. }
  270. // 获取注册归属数据列表
  271. public function getSubUserList()
  272. {
  273. $input = input();
  274. // 请求限流:一分钟三次(使用ThinkPHP5的Cache方式)
  275. $limitKey = 'jh_api_getSubUserList_limit:' . md5(json_encode($input));
  276. try {
  277. $redis = Cache::store('redis');
  278. $requestCount = $redis->get($limitKey);
  279. if ($requestCount === false || $requestCount === null) {
  280. // 首次请求,设置为1并设置过期时间
  281. $redis->set($limitKey, 1, 60);
  282. $requestCount = 1;
  283. } else {
  284. // 增加计数
  285. $requestCount = $redis->inc($limitKey);
  286. }
  287. if ($requestCount > 3) {
  288. return json(["code" => -130, "msg" => '请求过于频繁,请稍后再试(每分钟限制3次)!', "data" => []]);
  289. }
  290. } catch (\Exception $e) {
  291. // log_message('Redis连接失败: ' . $e->getMessage(), 'error', LOG_PATH . 'jh_log/');
  292. return json(["code" => -130, "msg" => '当前脚本程序执行异常,请联系开发!', "data" => []]);
  293. }
  294. $signRes = $this->sign($input);
  295. // dump($signRes);
  296. if (empty($input['sign']) || $signRes != $input['sign']) {
  297. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  298. }
  299. if (empty($input['date'])) {
  300. return json(["code" => -120, "msg" => '参数错误,缺少date参数!', "data" => []]);
  301. }
  302. $where = [
  303. 'nc.gh_status' => 1
  304. ];
  305. if (!empty($input['date']) && empty($input['uid'])) {
  306. $where['ns.create_time'] = ['between', [strtotime($input['date'] . ' 00:00:00'), strtotime($input['date'] . ' 23:59:59')]];
  307. }
  308. if (!empty($input['uid'])) {
  309. $where['ns.id'] = ['>', $input['uid']];
  310. }
  311. $list = Db::table('nw_subaccount')->alias('ns')
  312. ->join('cy_members cm', 'ns.member_id = cm.id', 'left')
  313. ->join('cy_game cg', 'ns.game_id = cg.id', 'left')
  314. ->join('nw_channel nc', 'ns.channel_id = nc.id', 'left') // 推广员 (level 3)
  315. ->join('nw_channel p1', 'nc.parent_id = p1.id', 'left') // 子会长
  316. ->join('nw_channel p2', 'p1.parent_id = p2.id', 'left') // 会长
  317. ->where($where)
  318. ->order('ns.id asc')
  319. ->field([
  320. 'ns.id', // 子账户ID
  321. 'ns.member_id as mid', // 账户ID - 2
  322. 'cm.username', // 账号 - 3
  323. 'cm.nickname', // 昵称 - 4
  324. 'cg.id as game_id', // 游戏ID - 5
  325. 'cg.name as game_name', // 游戏名 - 6
  326. 'nc.id as channel_id', // 推广员ID - 10
  327. 'nc.name as channel_name', // 推广员名称
  328. 'p1.id as business_id', // 子会长ID - 11
  329. 'p1.name as business_name', // 子会长名称
  330. 'p2.id as sub_president_id', // 会长ID - 12
  331. 'p2.name as sub_president_name', // 会长名称
  332. 'FROM_UNIXTIME(ns.create_time, "%Y-%m-%d %H:%i:%s") as reg_time', // 注册时间 - 14
  333. 'FROM_UNIXTIME(ns.update_time, "%Y-%m-%d %H:%i:%s") as login_time', // 登录时间 - 15
  334. ])
  335. ->paginate(null, false, ['page' => $input['page'] ?? 1, 'list_rows' => $input['limit'] ?? 100]);
  336. return json(["code" => 200, "msg" => 'success', "data" => $list ?? []]);
  337. }
  338. // 获取订单数据列表
  339. public function getPayList()
  340. {
  341. $input = input();
  342. $signRes = $this->sign($input);
  343. // dump($signRes);
  344. if (empty($input['sign']) || $signRes != $input['sign']) {
  345. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  346. }
  347. if (empty($input['time_type'])) {
  348. return json(["code" => -111, "msg" => '参数错误,缺少 time_type 参数!', "data" => []]);
  349. }
  350. if (empty($input['start_time'])) {
  351. return json(["code" => -112, "msg" => '参数错误,缺少 start_time 参数!', "data" => []]);
  352. }
  353. if (empty($input['end_time'])) {
  354. return json(["code" => -113, "msg" => '参数错误,缺少 end_time 参数!', "data" => []]);
  355. }
  356. if(empty($input['limit']) && intval($input['limit']) > 1000){
  357. return json(["code" => -114, "msg" => '参数错误,limit参数不能大于1000!', "data" => []]);
  358. }
  359. // // 请求限流:一分钟三次(使用ThinkPHP5的Cache方式)
  360. // $limitKey = 'jh_api_getPayList_limit:' . md5(json_encode($input));
  361. // try {
  362. // $redis = Cache::store('redis')->handler();
  363. // $requestCount = $redis->incr($limitKey); // 原子递增
  364. // if ($requestCount === 1) {
  365. // $redis->expire($limitKey, 60);
  366. // }
  367. // if ($requestCount > 10) {
  368. // return json(["code" => -130, "msg" => '请求过于频繁!', "data" => []]);
  369. // }
  370. // } catch (\Exception $e) {
  371. // return json(["code" => -130, "msg" => '系统异常!', "data" => []]);
  372. // }
  373. // 处理日期:支持 '2026-02-13' 或 '2026-02-13 13:55:55' 两种格式
  374. $startTime = strtotime($input['start_time']);
  375. $endTime = strtotime($input['end_time']);
  376. $timeField = 'cp.create_time';
  377. if($input['time_type'] == 2){
  378. // 按照支付时间查询
  379. $timeField = 'cp.pay_time';
  380. }
  381. $where = [
  382. 'cp.status' => 1,
  383. 'nc.gh_status' => 1,
  384. $timeField => ['between', [$startTime, $endTime]],
  385. ];
  386. $payTypeList = config('paytype')??[];
  387. if(!$payTypeList){
  388. return json(["code" => -115, "msg" => '缺少支付配置,请联系开发处理!', "data" => []]);
  389. }
  390. // 查询测试账户ID列表(约10个,变化不频繁)
  391. $testMemberIds = Db::table('cy_members')
  392. ->where('test_status', 1)
  393. ->column('id');
  394. // 如果有测试账户,加入查询条件
  395. if (!empty($testMemberIds)) {
  396. $where['cp.userid'] = ['not in', $testMemberIds];
  397. }
  398. // 查询内容=账号/昵称/游戏ID/游戏名/推广员/子会长/公会/注册时间/登录时间/支付方式/支付金额/代金券金额/订单号/充值金额/发货状态/角色ID/角色名/区服名
  399. $list = Db::table('cy_pay')->alias('cp')
  400. ->join('nw_subaccount ns', 'cp.userid = ns.member_id AND cp.gameid=ns.game_id', 'left')
  401. ->join('cy_game cg', 'cp.gameid = cg.id', 'left')
  402. ->join('nw_channel nc', 'cp.channel_id = nc.id', 'left') // 推广员 (level 3)
  403. // ->join('nw_channel p1', 'nc.parent_id = p1.id', 'left') // 子会长
  404. // ->join('nw_channel p2', 'p1.parent_id = p2.id', 'left') // 会长
  405. ->where($where)
  406. ->field([
  407. 'cp.id',
  408. 'cp.orderid as order_id', // 订单号 - 0
  409. 'cp.username as account', // 账号 - 2
  410. // 'cm.nickname', // 昵称
  411. 'cp.gameid as game_id', // 游戏ID - 1
  412. 'cg.name as game_name', // 游戏名
  413. 'nc.id as channel_id', // 推广员ID - 13
  414. 'nc.name as channel_name', // 推广员名称
  415. // 'p1.id as business_id', // 子会长ID
  416. // 'p1.name as business_name', // 子会长名称
  417. // 'p2.id as sub_president_id', // 会长ID
  418. // 'p2.name as sub_president_name', // 会长名称
  419. // 'ns.create_time reg_time', // 注册时间
  420. // 'ns.update_time as login_time', // 登录时间
  421. 'cp.paytype as pay_method', // 支付方式 - 10
  422. // 'cp.real_amount as pay_amount', // 支付金额(人民币部分) - 9 - 删掉
  423. 'cp.real_amount', // 第三方支付金额(可能为空,因为有平台币支付)
  424. 'cp.pay_amount', // 实付金额(不会有空,订单支付价格,处理过折扣、代金卷)
  425. // ## 代金卷 ##
  426. 'cp.coupon_member_id', // 代金券ID
  427. 'cp.coupon_amount', // 代金券金额
  428. // ## 折扣 ##
  429. 'cp.discount', // 游戏折扣
  430. 'cp.amount as recharge_amount', // 充值金额(订单总金额) - 5
  431. 'cp.real_coin as real_ptb', // 平台币 - 6
  432. // 'cp.status as delivery_status', // 发货状态:0=未支付、1=已支付发货
  433. 'cp.roleid as role_id', // 角色ID - 21
  434. 'cp.rolename as role_name', // 角色名 - 17
  435. 'cp.servername as server_name', // 区服名 - 19
  436. 'FROM_UNIXTIME(cp.create_time, "%Y-%m-%d %H:%i:%s") as create_time', // 下单时间
  437. 'FROM_UNIXTIME(cp.pay_time, "%Y-%m-%d %H:%i:%s") as pay_time', // 支付时间 - 26
  438. 'paytype', // 支付方式
  439. 'client_type', // 客户端类型(1=祈盟sdk, 2=巨量, 3=快手磁力包)
  440. ])
  441. ->order('cp.id desc')
  442. ->paginate(null, false, ['page' => intval($input['page'] ?? 1), 'list_rows' => intval($input['limit'] ?? 100)])
  443. ->each(function($item, $key) use ($payTypeList){
  444. $item['pay_method'] = $payTypeList[$item['pay_method']] ?? $item['pay_method'];
  445. // ## 订单类型: 1=普通单、2=平台币、3=代金卷、4=折扣
  446. $pay_type = 1;
  447. if($item['real_ptb'] > 0){
  448. $pay_type = 2;
  449. } elseif ($item['coupon_member_id'] > 0 && $item['coupon_amount'] > 0){
  450. $pay_type = 3;
  451. } elseif ($item['discount'] > 0){
  452. $pay_type = 4;
  453. }
  454. $item['pay_form'] = $pay_type;
  455. $item['real_amount'] = $item['real_amount'];
  456. return $item;
  457. });
  458. // 统计当天的订单总数和订单总金额
  459. $total_amount = Db::table('cy_pay')->alias('cp')
  460. ->join('nw_channel nc', 'cp.channel_id = nc.id AND nc.gh_status=1')
  461. ->where($where)
  462. ->sum('cp.pay_amount');
  463. $total_amount = sprintf("%.2f", $total_amount ?? 0.00);
  464. $resultList = $list->toArray();
  465. $resultList['total_amount'] = (float)$total_amount; // 订单总金额
  466. return json(["code" => 200, "msg" => 'success', "data" => $resultList ?? []]);
  467. }
  468. /**
  469. * 获取玩家活跃数据列表。
  470. *
  471. * active_start_hour、active_end_hour 均按小时传入,结束小时包含在查询范围内。
  472. * 单次最多查询 24 个小时,并过滤测试账号和非聚合渠道。
  473. */
  474. public function getPlayerActive()
  475. {
  476. $input = input();
  477. $signRes = $this->sign($input);
  478. if (empty($input['sign']) || $signRes != $input['sign']) {
  479. return json(["code" => -110, "msg" => '签名有误!', "data" => []]);
  480. }
  481. $activeStartHour = trim($input['active_start_hour'] ?? '');
  482. $activeEndHour = trim($input['active_end_hour'] ?? '');
  483. if ($activeStartHour === '') {
  484. return json(["code" => -120, "msg" => '参数错误,缺少 active_start_hour 参数!', "data" => []]);
  485. }
  486. if ($activeEndHour === '') {
  487. return json(["code" => -120, "msg" => '参数错误,缺少 active_end_hour 参数!', "data" => []]);
  488. }
  489. // 支持 YYYY-MM-DD HH 和 YYYY-MM-DD HH:00:00 两种整点格式。
  490. if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}$/', $activeStartHour)) {
  491. $activeStartHour .= ':00:00';
  492. }
  493. if (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}$/', $activeEndHour)) {
  494. $activeEndHour .= ':00:00';
  495. }
  496. $hourPattern = '/^\d{4}-\d{2}-\d{2} \d{2}:00:00$/';
  497. $startTimestamp = preg_match($hourPattern, $activeStartHour) ? strtotime($activeStartHour) : false;
  498. $endTimestamp = preg_match($hourPattern, $activeEndHour) ? strtotime($activeEndHour) : false;
  499. if (
  500. $startTimestamp === false
  501. || date('Y-m-d H:00:00', $startTimestamp) !== $activeStartHour
  502. || $endTimestamp === false
  503. || date('Y-m-d H:00:00', $endTimestamp) !== $activeEndHour
  504. ) {
  505. return json(["code" => -120, "msg" => '参数错误,活跃时间格式应为 YYYY-MM-DD HH 或 YYYY-MM-DD HH:00:00!', "data" => []]);
  506. }
  507. if ($startTimestamp > $endTimestamp) {
  508. return json(["code" => -120, "msg" => '参数错误,active_start_hour 不能晚于 active_end_hour!', "data" => []]);
  509. }
  510. // 结束小时包含在查询范围内,所以 SQL 上界取结束小时的下一整点(不包含)。
  511. $endExclusiveTimestamp = $endTimestamp + 3600;
  512. if (($endExclusiveTimestamp - $startTimestamp) > 24 * 3600) {
  513. return json(["code" => -120, "msg" => '参数错误,单次活跃时间范围不能超过 24 小时!', "data" => []]);
  514. }
  515. $page = intval($input['page'] ?? 1);
  516. $limit = intval($input['limit'] ?? 100);
  517. if ($page < 1) {
  518. return json(["code" => -120, "msg" => '参数错误,page 必须大于等于 1!', "data" => []]);
  519. }
  520. if ($limit < 1 || $limit > 1000) {
  521. return json(["code" => -120, "msg" => '参数错误,limit 必须在 1 到 1000 之间!', "data" => []]);
  522. }
  523. $gameId = intval($input['game_id'] ?? 0);
  524. $channelId = intval($input['channel_id'] ?? 0);
  525. if ($gameId < 0 || $channelId < 0) {
  526. return json(["code" => -120, "msg" => '参数错误,game_id 和 channel_id 不能为负数!', "data" => []]);
  527. }
  528. $where = [
  529. 'a.active_hour' => [
  530. ['>=', date('Y-m-d H:00:00', $startTimestamp)],
  531. ['<', date('Y-m-d H:00:00', $endExclusiveTimestamp)],
  532. ],
  533. ];
  534. if ($gameId > 0) {
  535. $where['a.game_id'] = $gameId;
  536. }
  537. if ($channelId > 0) {
  538. $where['a.channel_id'] = $channelId;
  539. }
  540. try {
  541. $list = Db::table('cy_player_active_log')->alias('a')
  542. ->join('cy_members m', 'm.id=a.member_id AND m.test_status=0')
  543. ->join('nw_channel channel', 'channel.id=a.channel_id AND channel.gh_status=1')
  544. // ->join('nw_channel channel', 'channel.id=a.channel_id')
  545. ->join('nw_game_server game_server', 'game_server.id=a.server_id AND game_server.game_id=a.game_id', 'left')
  546. ->field([
  547. 'a.id',
  548. 'a.member_id',
  549. 'a.subaccount_id',
  550. 'm.username',
  551. 'a.game_id',
  552. 'a.channel_id',
  553. 'channel.name as channel_name',
  554. 'a.server_id as server_pk',
  555. 'COALESCE(game_server.serverid, a.server_id) as server_id',
  556. 'COALESCE(game_server.servername, "") as server_name',
  557. 'a.role_id',
  558. 'a.role_name',
  559. 'a.role_level',
  560. 'a.active_hour',
  561. 'a.created_at',
  562. ])
  563. ->where($where)
  564. ->order('a.active_hour asc,a.id asc')
  565. ->paginate($limit, false, ['page' => $page]);
  566. return json(["code" => 200, "msg" => 'success', "data" => $list->toArray()]);
  567. } catch (\Exception $e) {
  568. log_message('getPlayerActive exception: ' . $e->getMessage(), 'error', LOG_PATH . 'jh_log/');
  569. return json(["code" => -500, "msg" => '系统异常,请联系开发处理!', "data" => []]);
  570. }
  571. }
  572. }