OnlineUserService.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <?php
  2. namespace App\Services;
  3. use App\Models\User;
  4. use App\Models\UserLogin;
  5. use Carbon\Carbon;
  6. /**
  7. * 在线用户列表(只读,不写库、不打外网)
  8. */
  9. class OnlineUserService
  10. {
  11. /** 最近活跃秒数 */
  12. public const ONLINE_WINDOW_SECONDS = 1800;
  13. /**
  14. * @param array{page?:int,limit?:int,member_id?:string,first_name?:string,login_ip?:string,platform?:string} $params
  15. * @return array{total:int,data:list<array>}
  16. */
  17. public static function list(array $params): array
  18. {
  19. $page = max(1, (int)($params['page'] ?? 1));
  20. $limit = min(200, max(1, (int)($params['limit'] ?? 15)));
  21. $since = time() - self::ONLINE_WINDOW_SECONDS;
  22. $latestLogin = UserLogin::query()
  23. ->selectRaw('user_id, MAX(id) as id')
  24. ->where('status', UserLogin::STATUS_SUCCESS)
  25. ->groupBy('user_id');
  26. $query = User::query()
  27. ->from('users')
  28. ->leftJoin('wallets', 'wallets.member_id', '=', 'users.member_id')
  29. ->leftJoinSub($latestLogin, 'latest_login', function ($join) {
  30. $join->on('latest_login.user_id', '=', 'users.user_id');
  31. })
  32. ->leftJoin('user_login as ul', 'ul.id', '=', 'latest_login.id')
  33. ->where('users.last_active_time', '>=', $since)
  34. ->where(function ($q) {
  35. $q->whereNull('users.from')->orWhere('users.from', '<>', '2');
  36. });
  37. if (!empty($params['member_id'])) {
  38. $mid = $params['member_id'];
  39. $query->where(function ($q) use ($mid) {
  40. $q->where('users.member_id', $mid)->orWhere('users.user_id', $mid);
  41. });
  42. }
  43. if (!empty($params['first_name'])) {
  44. $query->where('users.first_name', 'like', '%' . $params['first_name'] . '%');
  45. }
  46. if (!empty($params['login_ip'])) {
  47. $query->where('ul.login_ip', 'like', '%' . $params['login_ip'] . '%');
  48. }
  49. if (!empty($params['platform']) && $params['platform'] !== '全部') {
  50. $query->where('ul.platform', $params['platform']);
  51. }
  52. $query->select([
  53. 'users.id',
  54. 'users.member_id',
  55. 'users.user_id',
  56. 'users.first_name',
  57. 'users.last_active_time',
  58. 'wallets.available_balance',
  59. 'ul.login_ip',
  60. 'ul.country',
  61. 'ul.login_domain',
  62. 'ul.platform',
  63. 'ul.browser',
  64. 'ul.os',
  65. 'ul.created_at as login_time',
  66. ]);
  67. $total = (int)(clone $query)->toBase()->distinct()->count('users.id');
  68. $list = $query
  69. ->orderByDesc('users.last_active_time')
  70. ->forPage($page, $limit)
  71. ->get()
  72. ->map(function ($row) {
  73. return [
  74. 'member_id' => $row->member_id ?: $row->user_id,
  75. 'first_name' => (string)($row->first_name ?? ''),
  76. 'available_balance' => (float)($row->available_balance ?? 0),
  77. 'login_ip' => (string)($row->login_ip ?? ''),
  78. 'location' => (string)($row->country ?? ''),
  79. 'login_domain' => (string)($row->login_domain ?? ''),
  80. 'platform' => (string)($row->platform ?: self::guessPlatformFromOs((string)($row->os ?? ''))),
  81. 'browser' => (string)($row->browser ?? ''),
  82. 'os' => (string)($row->os ?? ''),
  83. 'login_time' => self::formatTime($row->login_time ?? null),
  84. 'last_active_time' => $row->last_active_time,
  85. ];
  86. })
  87. ->values()
  88. ->all();
  89. return ['total' => $total, 'data' => $list];
  90. }
  91. private static function formatTime($value): string
  92. {
  93. if (empty($value)) {
  94. return '';
  95. }
  96. try {
  97. return Carbon::parse($value)->setTimezone('Asia/Shanghai')->format('Y-m-d H:i:s');
  98. } catch (\Throwable $e) {
  99. return (string)$value;
  100. }
  101. }
  102. private static function guessPlatformFromOs(string $os): string
  103. {
  104. $os = strtolower($os);
  105. if (str_contains($os, 'android')) {
  106. return 'Android';
  107. }
  108. if (str_contains($os, 'ios') || str_contains($os, 'iphone') || str_contains($os, 'ipad')) {
  109. return 'iOS';
  110. }
  111. return 'PC';
  112. }
  113. }