src/Controller/Client/MarzbanController.php line 41

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Client;
  3. use App\Entity\VPN\Service\Service;
  4. use App\Entity\VPN\Service\ServiceUsage;
  5. use App\Entity\VPN\V2ray\Server;
  6. use App\Repository\VPN\Service\ServiceRepository;
  7. use App\Repository\VPN\Service\ServiceUsageRepository;
  8. use App\Repository\VPN\V2ray\ServerRepository;
  9. use App\Service\Util\MarzbanNewAPI;
  10. use App\Service\Util\NotificationService;
  11. use App\Service\Util\ServersAnalyticsUsageService;
  12. use App\Service\Util\Telegram;
  13. use DateTime;
  14. use Doctrine\ORM\EntityManagerInterface;
  15. use Exception;
  16. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  17. use Symfony\Component\HttpFoundation\JsonResponse;
  18. use Symfony\Component\HttpFoundation\Request;
  19. use Symfony\Component\Routing\Annotation\Route;
  20. use Symfony\Component\Stopwatch\Stopwatch;
  21. //class MarzbanController extends AbstractController
  22. //{
  23. //    public $registry;
  24. //    private Telegram $telegram;
  25. //    public EntityManagerInterface $entityManager;
  26. //
  27. //    public ServiceUsageRepository $serviceUsageRepository;
  28. //
  29. //    public function __construct(Telegram $telegram, EntityManagerInterface $entityManager , ServiceUsageRepository $serviceUsageRepository)
  30. //    {
  31. //        $this->telegram = $telegram;
  32. //        $this->serviceUsageRepository = $serviceUsageRepository;
  33. //        $this->entityManager = $entityManager;
  34. //    }
  35. //
  36. //
  37. ////    Entity Manager Is Closed Problem
  38. ////    #[Route('/client/marzban', name: 'app_client_marzban')]
  39. ////    public function index(
  40. ////        Request           $request,
  41. ////        ServiceRepository $serviceRepository,
  42. ////        ServerRepository $serverRepository,
  43. ////        NotificationService $notificationService
  44. ////    ): JsonResponse
  45. ////    {
  46. ////
  47. //////        return new JsonResponse('TEst', JsonResponse::HTTP_BAD_REQUEST);
  48. ////
  49. ////
  50. ////        $entityManager = $this->getDoctrine()->getManager();
  51. ////        $this->registry = $entityManager;
  52. ////        $now = new DateTime();
  53. ////        $currentHour = (int)$now->format('H');
  54. ////        $data = json_decode($request->getContent(), true);
  55. ////        if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
  56. ////            return new JsonResponse('Invalid JSON or missing data', JsonResponse::HTTP_BAD_REQUEST);
  57. ////        }
  58. ////
  59. ////        sleep(random_int(60 , 500));
  60. ////        $stopwatch = new Stopwatch();
  61. ////        // Start the stopwatch
  62. ////        $stopwatch->start('duration');
  63. ////        $users = $data['list'];
  64. ////        $usersCount = count($users);
  65. ////        $ip = $data['ip'];
  66. ////        $totalServerUsage = 0;
  67. ////        foreach ($users as $key => $userItem) {
  68. ////            if ($userItem['used_traffic'] > 0) {
  69. ////                $totalServerUsage = $totalServerUsage + $userItem['used_traffic'];
  70. ////            }
  71. ////        }
  72. ////        $analytics = new ServersAnalyticsUsageService($ip, $this->entityManager);
  73. ////        $analytics->addServerUsage($ip, $totalServerUsage);
  74. ////        $server = $this->getServer($ip, $serverRepository);
  75. ////        if (!$server) {
  76. ////            $this->telegram->sendRawMessage($ip);
  77. ////            return new JsonResponse('Server not found', JsonResponse::HTTP_BAD_REQUEST);
  78. ////        }
  79. ////        $server->setLastScriptUpdate(new \DateTimeImmutable());
  80. ////        $this->entityManager->flush();
  81. ////        // Split users into batches of 50 to handle them in smaller transactions
  82. ////        $arrayData = array_chunk($users, 50);
  83. ////        foreach ($arrayData as $arrayDatum) {
  84. ////            try {
  85. ////                // Start a transaction for this batch
  86. ////                $entityManager->beginTransaction();
  87. ////
  88. ////                foreach ($arrayDatum as $userArray) {
  89. ////                    try {
  90. ////                        $username = $userArray['username'];
  91. ////                        $calculationVolume = 1.2;
  92. ////                        if ($server->getCountry()->getCode() === 'ir'){
  93. ////                            $calculationVolume = 2;
  94. ////                        }
  95. ////                        foreach ($server->getProtocols() as $protocol) {
  96. ////                            if ($protocol->getCountry() !== null &&  $protocol->getCountry()->getCode() === 'ir'){
  97. ////                                $calculationVolume = 2;
  98. ////                            }
  99. ////                        }
  100. ////                        $usedTraffic = $userArray['used_traffic'] ?? 0;
  101. ////                        $usedTraffic = $usedTraffic * $calculationVolume;
  102. ////
  103. ////
  104. ////
  105. ////
  106. ////
  107. ////                        // Only handle users with traffic usage
  108. ////                        if ($usedTraffic > 0) {
  109. ////                            $service = $this->findService($username,$serviceRepository);
  110. ////                            if ($service) {
  111. ////                                $this->handleServiceUsage($server, $service, $usedTraffic);
  112. ////                                if (!$service->getExpireAt())
  113. ////                                {
  114. ////                                    $now = new \DateTimeImmutable();
  115. ////                                    $expiredAt =  $now->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
  116. ////                                    $service->setExpireAt($expiredAt);
  117. ////                                    $entityManager->flush();
  118. ////                                }
  119. ////                                if ($service->getPlan()->getServiceType() === 'unlimited' && $service->getCountry() !== $server->getCountry()->getCode()){
  120. ////                                    $this->removeServiceUser($server, $username);
  121. ////                                }
  122. ////                            }else{
  123. ////                                $this->removeServiceUser($server, $username);
  124. ////                            }
  125. ////
  126. ////
  127. ////
  128. ////
  129. ////
  130. ////                        }
  131. ////                    } catch (Exception $exception) {
  132. ////                        // Log the exception without affecting the entire transaction
  133. //////                        $this->telegram->exception($exception);
  134. ////                        $notificationService->exception($exception);
  135. ////                    }
  136. ////                }
  137. ////
  138. ////                // Commit the current transaction for the batch
  139. ////                $entityManager->flush();
  140. ////                $entityManager->commit();
  141. ////
  142. ////                // Clear the entity manager to free up memory
  143. ////                $entityManager->clear();
  144. ////
  145. ////            } catch (Exception $e) {
  146. ////                // Catch any exception and log it
  147. //////                $this->telegram->exception($e);
  148. ////$notificationService->exception($e);
  149. ////                // If EntityManager is closed due to an error, reset it
  150. ////                if (!$entityManager->isOpen()) {
  151. ////                    $entityManager = $this->getDoctrine()->getManager();
  152. ////                }
  153. ////            } finally {
  154. ////                // Clear the entity manager to free up memory after each batch
  155. ////                $entityManager->clear();
  156. ////            }
  157. ////        }
  158. ////
  159. ////        // Stop the stopwatch and get the event duration
  160. ////        $event = $stopwatch->stop('duration');
  161. ////
  162. ////        // Get elapsed time in milliseconds
  163. ////        $duration = $event->getDuration();  // Time in milliseconds
  164. //////        $this->telegram->sendRawMessage(
  165. //////            'Duration : '. $duration .' ms'.PHP_EOL.
  166. //////            'Ip : '. $ip .PHP_EOL.
  167. //////            'Users Trafical : '. count($users).PHP_EOL.
  168. //////            'Total Users : '.$usersCount
  169. //////
  170. //////        );
  171. ////        return new JsonResponse('Success', JsonResponse::HTTP_OK);
  172. ////
  173. ////
  174. ////    }
  175. //
  176. //
  177. //
  178. //
  179. //    #[Route('/client/marzban', name: 'app_client_marzban')]
  180. //    public function index(
  181. //        Request $request,
  182. //        ServiceRepository $serviceRepository,
  183. //        ServerRepository $serverRepository,
  184. //        NotificationService $notificationService
  185. //    ): JsonResponse
  186. //    {
  187. //        $now = new DateTime();
  188. //        $data = json_decode($request->getContent(), true);
  189. //        if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
  190. //            return new JsonResponse('Invalid JSON or missing data', JsonResponse::HTTP_BAD_REQUEST);
  191. //        }
  192. //        sleep(random_int(100, 500));
  193. //        $users = $data['list'];
  194. //        $usersCount = count($users);
  195. //        $ip = $data['ip'];
  196. //        $totalServerUsage = array_reduce($users, function ($carry, $user) {
  197. //            return $carry + ($user['used_traffic'] ?? 0);
  198. //        }, 0);
  199. //
  200. //        $analytics = new ServersAnalyticsUsageService($ip, $this->entityManager);
  201. //        $analytics->addServerUsage($ip, $totalServerUsage);
  202. //        $server = $this->getServer($ip, $serverRepository);
  203. //        if (!$server) {
  204. //            $this->telegram->sendRawMessage(
  205. //                'این سرور داره کار میکنه ولی تو پنل نیست که بشه حجم به سرویس ها اضافه بشه : '.
  206. //                $ip);
  207. ////            return new JsonResponse('Server not found', JsonResponse::HTTP_BAD_REQUEST);
  208. //        }
  209. //
  210. //        $server?->setLastScriptUpdate(new \DateTimeImmutable());
  211. //        $this->ensureEntityManagerOpen();
  212. //        $this->flush();
  213. //
  214. //        $arrayData = array_chunk($users, 50);
  215. //        foreach ($arrayData as $arrayDatum) {
  216. //            try {
  217. //                $this->ensureEntityManagerOpen();
  218. //                $this->entityManager->beginTransaction();
  219. //
  220. //                foreach ($arrayDatum as $userArray) {
  221. //                    try {
  222. //                        $username = $userArray['username'];
  223. //                        $usedTraffic = ($userArray['used_traffic'] ?? 0);
  224. ////                        $calculationVolume = 1.2;
  225. //                        $calculationVolume = 2;
  226. //                        if ($server->getCountry()->getCode() === 'ir') {
  227. //                            $calculationVolume = 2;
  228. //                        }
  229. //
  230. //                        foreach ($server->getProtocols() as $protocol) {
  231. //                            if ($protocol->getCountry()?->getCode() === 'ir') {
  232. //                                $calculationVolume = 2;
  233. //                            }
  234. //                        }
  235. //
  236. //                        $usedTraffic *= $calculationVolume;
  237. //
  238. //                        if ($usedTraffic > 0) {
  239. //                            $service = $this->findService($username, $serviceRepository);
  240. //                            if ($service) {
  241. //                                $this->handleServiceUsage($server, $service, $usedTraffic);
  242. //                                if (!$service->getExpireAt()) {
  243. //                                    $expiredAt = (new \DateTimeImmutable())->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
  244. //                                    $service->setExpireAt($expiredAt);
  245. //                                    $this->ensureEntityManagerOpen();
  246. //                                    $this->flush();
  247. //                                }
  248. //                                if ($service->getPlan()->getServiceType() === 'unlimited' &&
  249. //                                    $service->getCountry() !== $server->getCountry()->getCode()) {
  250. //                                    $this->removeServiceUser($server, $username);
  251. //                                }
  252. //                            } else {
  253. //                                $this->removeServiceUser($server, $username);
  254. //                            }
  255. //                        }
  256. //
  257. //                    } catch (Exception $exception) {
  258. //                        $notificationService->exception($exception);
  259. //                    }
  260. //                }
  261. //
  262. //                $this->ensureEntityManagerOpen();
  263. //                $this->flush();
  264. //                $this->entityManager->commit();
  265. //
  266. //            } catch (Exception $e) {
  267. //                $notificationService->exception($e);
  268. //                if (!$this->entityManager->isOpen()) {
  269. //                    $this->entityManager = $this->getDoctrine()->getManager();
  270. //                }
  271. //            } finally {
  272. //                $this->ensureEntityManagerOpen();
  273. //                $this->entityManager->clear();
  274. //            }
  275. //        }
  276. //        return new JsonResponse('Success', JsonResponse::HTTP_OK);
  277. //    }
  278. //
  279. //
  280. //    private function getServer(
  281. //        string           $ip,
  282. //        ServerRepository $v2rayMultiServerRepository,
  283. //    ): Server|null
  284. //    {
  285. //        return $v2rayMultiServerRepository->findOneBy(['ip' => $ip, 'active' => 1, 'deletedAt' => null]);
  286. //    }
  287. //
  288. //    private function findService(
  289. //        string            $username,
  290. //        ServiceRepository $v2rayMultiServiceRepository,
  291. //    ): Service|null
  292. //    {
  293. //        return $v2rayMultiServiceRepository->findOneBy(['clientId' => $username]);
  294. //    }
  295. //
  296. //    private function handleServiceUsage(Server $server, Service $service, int $usedTraffic): void
  297. //    {
  298. //        if ($usedTraffic > 0) {
  299. //            $trafficToAdd = $usedTraffic;
  300. //            $service->setV2rayUsage($service->getV2rayUsage() + $trafficToAdd);
  301. //            $now = new \DateTime();
  302. //            $todayUsageObject = $this->serviceUsageRepository->findOneBy(['service' => $service , 'date' => $now]);
  303. //            if (!$todayUsageObject) {
  304. //                $todayUsageObject = new ServiceUsage();
  305. //                $todayUsageObject->setService($service);
  306. //                $todayUsageObject->setDate(new \DateTime());
  307. //                $this->entityManager->persist($todayUsageObject);
  308. //            }
  309. //            $todayUsageObject->setV2rayUsage(
  310. //                $todayUsageObject->getV2rayUsage() +
  311. //                (int)$trafficToAdd
  312. //            );
  313. //
  314. ////            TODO : check
  315. //
  316. ////            $this->entityManager->flush();
  317. //            if ((!$service->isEnabled() || !$service->isActive())) {
  318. //                $this->removeServiceUser($server, $service);
  319. //            }
  320. //
  321. //        }
  322. //    }
  323. //
  324. //
  325. //    private function removeServiceUser(Server $server, $service): void
  326. //    {
  327. //        $apiObject = new MarzbanNewAPI($server);
  328. //        if ($service instanceof Service){
  329. //            $apiObject->removeUser($service->getClientId());
  330. //        }else{
  331. //            $apiObject->removeUser($service);
  332. //        }
  333. //    }
  334. //
  335. //    private function ensureEntityManagerOpen(): void
  336. //    {
  337. //        if (!$this->entityManager->isOpen()) {
  338. //            $this->entityManager = $this->getDoctrine()->getManager();
  339. //        }
  340. //    }
  341. //
  342. //
  343. //    public function flush()
  344. //    {
  345. //        try {
  346. //            $this->entityManager->flush();
  347. //        } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
  348. //            $this->logDoctrineException($e);
  349. //            $this->resetEntityManager(); // 👈 این مهمه
  350. //        } catch (\Doctrine\DBAL\Exception\ConstraintViolationException $e) {
  351. //            $this->logDoctrineException($e);
  352. //            $this->resetEntityManager();
  353. //        } catch (\Doctrine\ORM\ORMException $e) {
  354. //            $this->logDoctrineException($e);
  355. //            $this->resetEntityManager();
  356. //        } catch (\Exception $e) {
  357. //            $this->logDoctrineException($e);
  358. //            $this->resetEntityManager();
  359. //        }
  360. //    }
  361. //
  362. //    private function logDoctrineException(\Exception $e): void
  363. //    {
  364. //        $this->telegram->sendRawMessage(
  365. //            "❌ Doctrine Error: " . $e->getMessage() . "\n\n" .
  366. //            "File: " . $e->getFile() . "\nLine: " . $e->getLine() . "\n\n" .
  367. //            "Trace:\n" . substr($e->getTraceAsString(), 0, 1000)
  368. //        );
  369. //    }
  370. //
  371. //    private function resetEntityManager(): void
  372. //    {
  373. //        if (!$this->entityManager->isOpen()) {
  374. //            $this->entityManager = $this->getDoctrine()->getManager();
  375. //        }
  376. //    }
  377. //
  378. //}
  379. class MarzbanController extends AbstractController
  380. {
  381.     private Telegram $telegram;
  382.     private EntityManagerInterface $entityManager;
  383.     private ServiceUsageRepository $serviceUsageRepository;
  384.     public function __construct(Telegram $telegramEntityManagerInterface $entityManagerServiceUsageRepository $serviceUsageRepository)
  385.     {
  386.         $this->telegram $telegram;
  387.         $this->entityManager $entityManager;
  388.         $this->serviceUsageRepository $serviceUsageRepository;
  389.     }
  390.     #[Route('/client/marzban'name'app_client_marzban')]
  391.     public function index(Request $requestServiceRepository $serviceRepositoryServerRepository $serverRepositoryNotificationService $notificationService): JsonResponse
  392.     {
  393.         $data json_decode($request->getContent(), true);
  394.         if (json_last_error() !== JSON_ERROR_NONE || !isset($data['ip'], $data['list'])) {
  395.             return new JsonResponse('Invalid JSON or missing data'JsonResponse::HTTP_BAD_REQUEST);
  396.         }
  397.         sleep(random_int(100500));
  398.         $users $data['list'];
  399.         $ip $data['ip'];
  400.         $totalServerUsage array_reduce($users, fn($carry$user) => $carry + ($user['used_traffic'] ?? 0), 0);
  401.         $analytics = new ServersAnalyticsUsageService($ip$this->entityManager);
  402.         $analytics->addServerUsage($ip$totalServerUsage);
  403.         $server $this->getServer($ip$serverRepository);
  404.         if (!$server) {
  405.             $this->telegram->sendRawMessage("سرور پیدا نشد: $ip");
  406.         }
  407.         $server?->setLastScriptUpdate(new \DateTimeImmutable());
  408.         $this->safeFlush();
  409.         foreach (array_chunk($users50) as $batch) {
  410.             try {
  411.                 $this->ensureEntityManagerOpen();
  412.                 $this->entityManager->beginTransaction();
  413.                 foreach ($batch as $user) {
  414.                     try {
  415.                         $username $user['username'] ?? null;
  416.                         $usedTraffic = ($user['used_traffic'] ?? 0) * 2;
  417.                         if ($usedTraffic <= || !$username) continue;
  418.                         $service $this->findService($username$serviceRepository);
  419.                         if ($service) {
  420.                             $this->handleServiceUsage($server$service$usedTraffic);
  421.                             if (!$service->getExpireAt()) {
  422.                                 $expiredAt = (new \DateTimeImmutable())->add(new \DateInterval("P{$service->getPlan()->getDays()}D"));
  423.                                 $service->setExpireAt($expiredAt);
  424.                                 $this->safeFlush();
  425.                             }
  426.                             if ($service->getPlan()->getServiceType() === 'unlimited' && $service->getCountry() !== $server->getCountry()->getCode()) {
  427.                                 $this->removeServiceUser($server$username);
  428.                             }
  429.                         } else {
  430.                             $this->removeServiceUser($server$username);
  431.                         }
  432.                     } catch (Exception $e) {
  433.                         $notificationService->exception($e);
  434.                     }
  435.                 }
  436.                 $this->safeFlush();
  437.                 if ($this->entityManager->getConnection()->isTransactionActive()) {
  438.                     $this->entityManager->commit();
  439.                 }
  440.             } catch (Exception $e) {
  441.                 $notificationService->exception($e);
  442.                 if ($this->entityManager->getConnection()->isTransactionActive()) {
  443.                     $this->entityManager->rollback();
  444.                 }
  445.                 $this->resetEntityManager();
  446.             } finally {
  447.                 $this->entityManager->clear();
  448.             }
  449.         }
  450.         return new JsonResponse('Success'JsonResponse::HTTP_OK);
  451.     }
  452.     private function getServer(string $ipServerRepository $repo): ?Server
  453.     {
  454.         return $repo->findOneBy(['ip' => $ip'active' => 1'deletedAt' => null]);
  455.     }
  456.     private function findService(string $usernameServiceRepository $repo): ?Service
  457.     {
  458.         return $repo->findOneBy(['clientId' => $username]);
  459.     }
  460.     private function handleServiceUsage(Server $serverService $serviceint $usedTraffic): void
  461.     {
  462.         $service->setV2rayUsage($service->getV2rayUsage() + $usedTraffic);
  463.         $today = new \DateTime();
  464.         $usage $this->serviceUsageRepository->findOneBy(['service' => $service'date' => $today]);
  465.         if (!$usage) {
  466.             $usage = new ServiceUsage();
  467.             $usage->setService($service);
  468.             $usage->setDate($today);
  469.             $this->entityManager->persist($usage);
  470.         }
  471.         $usage->setV2rayUsage($usage->getV2rayUsage() + $usedTraffic);
  472.         if (!$service->isEnabled() || !$service->isActive()) {
  473.             $this->removeServiceUser($server$service);
  474.         }
  475.     }
  476.     private function removeServiceUser(Server $server$service): void
  477.     {
  478.         $api = new MarzbanNewAPI($server);
  479.         $clientId $service instanceof Service $service->getClientId() : $service;
  480.         $api->removeUser($clientId);
  481.     }
  482.     private function ensureEntityManagerOpen(): void
  483.     {
  484.         if (!$this->entityManager->isOpen()) {
  485.             $this->resetEntityManager();
  486.         }
  487.     }
  488.     private function safeFlush(): void
  489.     {
  490.         try {
  491.             $this->entityManager->flush();
  492.         } catch (\Throwable $e) {
  493.             $this->logDoctrineException($e);
  494.             if ($this->entityManager->getConnection()->isTransactionActive()) {
  495.                 $this->entityManager->rollback();
  496.             }
  497.             $this->resetEntityManager();
  498.         }
  499.     }
  500.     private function resetEntityManager(): void
  501.     {
  502.         if (!$this->entityManager->isOpen()) {
  503.             $this->entityManager $this->getDoctrine()->getManager();
  504.         }
  505.     }
  506.     private function logDoctrineException(\Throwable $e): void
  507.     {
  508.         $this->telegram->sendRawMessage(
  509.             "🚨 Doctrine Exception 🚨\n" .
  510.             "📛 Message: " $e->getMessage() . "\n" .
  511.             "📁 File: " $e->getFile() . "\n" .
  512.             "📍 Line: " $e->getLine() . "\n" .
  513.             "🧵 Trace: " substr($e->getTraceAsString(), 02000)
  514.         );
  515.     }
  516. }