TaskDaily.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Models\Node;
  4. use App\Models\NodeDailyDataFlow;
  5. use App\Models\Ticket;
  6. use App\Models\User;
  7. use App\Notifications\TicketClosed;
  8. use App\Services\OrderService;
  9. use App\Utils\Helpers;
  10. use DB;
  11. use Illuminate\Console\Command;
  12. use Illuminate\Database\Eloquent\Builder;
  13. use Log;
  14. class TaskDaily extends Command
  15. {
  16. protected $signature = 'task:daily';
  17. protected $description = '每日任务';
  18. public function handle(): void
  19. {
  20. $jobTime = microtime(true);
  21. $this->expireUser(); // 过期用户处理
  22. $this->closeTickets(); // 关闭用户超时未处理的工单
  23. if (sysConfig('reset_traffic')) {
  24. $this->resetUserTraffic(); // 重置用户流量
  25. }
  26. if (sysConfig('auto_release_port')) {
  27. $this->releaseAccountPort(); // 账号端口回收
  28. }
  29. $this->userTrafficStatistics(); // 用户每日流量统计
  30. $this->nodeTrafficStatistics(); // 节点每日流量统计
  31. $jobTime = round(microtime(true) - $jobTime, 4);
  32. Log::info(__('----「:job」Completed, Used :time seconds ----', ['job' => $this->description, 'time' => $jobTime]));
  33. }
  34. private function expireUser(): void
  35. { // 过期用户处理
  36. $isBanStatus = sysConfig('is_ban_status');
  37. $dirtyWorks = [
  38. 'u' => 0,
  39. 'd' => 0,
  40. 'transfer_enable' => 0,
  41. 'enable' => 0,
  42. 'level' => 0,
  43. 'reset_time' => null,
  44. 'ban_time' => null,
  45. ]; // 清理账号 & 停止服务
  46. $banMsg = __('[Daily Task] Account Expiration: Stop Service');
  47. if ($isBanStatus) {
  48. $dirtyWorks['status'] = -1; // 封禁账号
  49. $banMsg = __('[Daily Task] Account Expiration: Block Login & Clear Account');
  50. }
  51. User::activeUser()->where('expired_at', '<', date('Y-m-d')) // 过期
  52. ->chunk(config('tasks.chunk'), function ($users) use ($banMsg, $dirtyWorks) {
  53. $users->each(function ($user) use ($banMsg, $dirtyWorks) {
  54. $user->update($dirtyWorks);
  55. Helpers::addUserTrafficModifyLog($user->id, $user->transfer_enable, 0, $banMsg);
  56. $user->banedLogs()->create(['description' => $banMsg]);
  57. });
  58. });
  59. }
  60. private function closeTickets(): void
  61. { // 关闭用户超时未处理的工单
  62. $closeTicketsHours = config('tasks.close.tickets');
  63. Ticket::whereStatus(1)->with('reply')->whereHas('reply', function ($query) {
  64. $query->where('admin_id', '<>', null);
  65. })->where('updated_at', '<=', now()->subHours($closeTicketsHours))->chunk(config('tasks.chunk'), function ($tickets) use ($closeTicketsHours) {
  66. $tickets->each(function ($ticket) use ($closeTicketsHours) {
  67. if ($ticket->close()) {
  68. $ticket->user->notify(new TicketClosed($ticket->id, $ticket->title, route('ticket.edit', $ticket),
  69. __('You have not responded this ticket in :num hours, System has closed your ticket.', ['num' => $closeTicketsHours])));
  70. }
  71. });
  72. });
  73. }
  74. private function resetUserTraffic(): void
  75. { // 重置用户流量
  76. $today = date('Y-m-d');
  77. User::where('status', '<>', -1)->where('expired_at', '>', $today)->where('reset_time', '<=', $today)->whereHas('orders', function ($query) {
  78. $query->activePlan();
  79. })->with(['orders' => function ($query) {
  80. $query->activePlan();
  81. }])->chunk(config('tasks.chunk'), function ($users) {
  82. $users->each(function ($user) {
  83. $user->orders()->activePackage()->update(['is_expire' => 1]); // 过期生效中的加油包
  84. $order = $user->orders->first(); // 取出用户正在使用的套餐
  85. $oldData = $user->transfer_enable;
  86. // 重置流量与重置日期
  87. if ($user->update((new OrderService($order))->resetTimeAndData($user->expired_at))) {
  88. Helpers::addUserTrafficModifyLog($order->user_id, $oldData, $user->transfer_enable, trans('[Daily Task] Reset Account Traffic, Next Reset Date: :date', ['date' => $user->reset_date]), $order->id);
  89. } else {
  90. Log::error(trans('notification.reset_failed', ['uid' => $user->id, 'username' => $user->username]));
  91. }
  92. });
  93. });
  94. }
  95. private function releaseAccountPort(): void
  96. { // 被封禁 / 过期N天 的账号自动释放端口
  97. User::where('port', '<>', 0)->where(function (Builder $query) {
  98. $query->whereStatus(-1)->orWhere('expired_at', '<=', date('Y-m-d', strtotime('-'.config('tasks.release_port').' days')));
  99. })->update(['port' => 0]);
  100. }
  101. private function userTrafficStatistics(): void
  102. {
  103. $created_at = date('Y-m-d 23:59:59', strtotime('yesterday'));
  104. $end = strtotime($created_at);
  105. $start = $end - 86399;
  106. User::activeUser()->whereHas('dataFlowLogs', function (Builder $query) use ($start, $end) {
  107. $query->whereBetween('log_time', [$start, $end]);
  108. })->with([
  109. 'dataFlowLogs' => function ($query) use ($start, $end) {
  110. $query->whereBetween('log_time', [$start, $end]);
  111. },
  112. ])->chunk(config('tasks.chunk'), function ($users) use ($created_at) {
  113. foreach ($users as $user) {
  114. $dataFlowLogs = $user->dataFlowLogs->groupBy('node_id');
  115. $data = $dataFlowLogs->map(function ($logs, $nodeId) use ($created_at) {
  116. $totals = $logs->reduce(function ($carry, $log) {
  117. $carry['u'] += $log['u'];
  118. $carry['d'] += $log['d'];
  119. return $carry;
  120. }, ['u' => 0, 'd' => 0]);
  121. return [
  122. 'node_id' => $nodeId,
  123. 'u' => $totals['u'],
  124. 'd' => $totals['d'],
  125. 'created_at' => $created_at,
  126. ];
  127. })->values()->all();
  128. $data[] = [ // 每日节点流量合计
  129. 'node_id' => null,
  130. 'u' => array_sum(array_column($data, 'u')),
  131. 'd' => array_sum(array_column($data, 'd')),
  132. 'created_at' => $created_at,
  133. ];
  134. $user->dailyDataFlows()->createMany($data);
  135. }
  136. });
  137. }
  138. private function nodeTrafficStatistics(): void
  139. {
  140. $created_at = date('Y-m-d 23:59:59', strtotime('yesterday'));
  141. $end = strtotime($created_at);
  142. $start = $end - 86399;
  143. Node::whereHas('userDataFlowLogs', function (Builder $query) use ($start, $end) {
  144. $query->whereBetween('log_time', [$start, $end]);
  145. })->withCount([
  146. 'userDataFlowLogs as u_sum' => function ($query) use ($start, $end) {
  147. $query->select(DB::raw('SUM(u)'))->whereBetween('log_time', [$start, $end]);
  148. },
  149. ])->withCount([
  150. 'userDataFlowLogs as d_sum' => function ($query) use ($start, $end) {
  151. $query->select(DB::raw('SUM(d)'))->whereBetween('log_time', [$start, $end]);
  152. },
  153. ])->chunk(config('tasks.chunk'), function ($nodes) use ($created_at) {
  154. foreach ($nodes as $node) {
  155. $node->dailyDataFlows()->create([
  156. 'u' => $node->u_sum,
  157. 'd' => $node->d_sum,
  158. 'created_at' => $created_at,
  159. ]);
  160. }
  161. });
  162. $dailyTotal = NodeDailyDataFlow::whereNotNull('node_id')->whereCreatedAt($created_at)->selectRaw('SUM(u) as total_u, SUM(d) as total_d')->first();
  163. if ($dailyTotal) {
  164. NodeDailyDataFlow::create([
  165. 'u' => $dailyTotal->total_u,
  166. 'd' => $dailyTotal->total_d,
  167. 'created_at' => $created_at,
  168. ]);
  169. }
  170. }
  171. }