Node.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Models;
  4. /**
  5. * Node Model
  6. *
  7. * @property-read int $id id
  8. *
  9. * @property string $name Display name
  10. * @property bool $type If node display @todo Correct column name and type
  11. * @property string $server Domain
  12. * @property string $method Crypt method @deprecated
  13. * @property string $info Infomation
  14. * @property string $status Status description
  15. * @property int $sort Node type @todo Correct column name to `type`
  16. * @property int $custom_method Customs node crypt @deprecated
  17. * @property float $traffic_rate Node traffic rate
  18. *
  19. * @todo More property
  20. *
  21. * @property bool $online If node is online
  22. * @property bool $gfw_block If node is blocked by GFW
  23. */
  24. use App\Services\Config;
  25. use App\Utils\Tools;
  26. use App\Utils\URL;
  27. final class Node extends Model
  28. {
  29. protected $connection = 'default';
  30. protected $table = 'node';
  31. protected $casts = [
  32. 'node_speedlimit' => 'float',
  33. 'traffic_rate' => 'float',
  34. 'mu_only' => 'int',
  35. 'sort' => 'int',
  36. 'type' => 'bool',
  37. 'node_heartbeat' => 'int',
  38. ];
  39. /**
  40. * 节点是否显示和隐藏
  41. */
  42. public function type(): string
  43. {
  44. return $this->type ? '显示' : '隐藏';
  45. }
  46. /**
  47. * 节点类型
  48. */
  49. public function sort(): string
  50. {
  51. switch ($this->sort) {
  52. case 0:
  53. $sort = 'Shadowsocks';
  54. break;
  55. case 9:
  56. $sort = 'Shadowsocks - 单端口多用户';
  57. break;
  58. case 11:
  59. $sort = 'V2Ray 节点';
  60. break;
  61. case 13:
  62. $sort = 'Shadowsocks - V2Ray-Plugin&Obfs';
  63. break;
  64. case 14:
  65. $sort = 'Trojan';
  66. break;
  67. default:
  68. $sort = '系统保留';
  69. }
  70. return $sort;
  71. }
  72. /**
  73. * 单端口多用户启用类型
  74. */
  75. public function muOnly(): string
  76. {
  77. switch ($this->mu_only) {
  78. case -1:
  79. $mu_only = '只启用普通端口';
  80. break;
  81. case 0:
  82. $mu_only = '单端口多用户与普通端口并存';
  83. break;
  84. case 1:
  85. $mu_only = '只启用单端口多用户';
  86. break;
  87. default:
  88. $mu_only = '错误类型';
  89. }
  90. return $mu_only;
  91. }
  92. /**
  93. * 节点最后活跃时间
  94. */
  95. public function nodeHeartbeat(): string
  96. {
  97. return date('Y-m-d H:i:s', $this->node_heartbeat);
  98. }
  99. /**
  100. * 获取节点在线时间
  101. */
  102. public function getNodeUptime(): string
  103. {
  104. $uptime = $this->uptime;
  105. if ($uptime === null) {
  106. return '未知';
  107. }
  108. return Tools::secondsToTime((int) $uptime);
  109. }
  110. /**
  111. * 获取节点负载
  112. */
  113. public function getNodeLoad(): float
  114. {
  115. $load = $this->load;
  116. if ($load === null) {
  117. return 0;
  118. }
  119. return (float) number_format(((float) explode(' ', $load)[0]), 2, '.', '');
  120. }
  121. public function getNodeUpRate()
  122. {
  123. $log = NodeOnlineLog::where('node_id', $this->id)->where('log_time', '>=', \time() - 86400)->count();
  124. return $log / 1440;
  125. }
  126. public function getNodeAlive()
  127. {
  128. return NodeOnlineLog::where('node_id', $this->id)->orderBy('id', 'desc')->whereRaw('`log_time`%1800<60')->limit(48)->get();
  129. }
  130. /**
  131. * 获取节点 5 分钟内最新的在线人数
  132. */
  133. public function getNodeOnlineUserCount(): int
  134. {
  135. if (\in_array($this->sort, [9])) {
  136. return -1;
  137. }
  138. $log = NodeOnlineLog::where('node_id', $this->id)->where('log_time', '>', \time() - 300)->orderBy('id', 'desc')->first();
  139. if ($log === null) {
  140. return 0;
  141. }
  142. return $log->online_user;
  143. }
  144. /**
  145. * 获取节点在线状态
  146. *
  147. * @return int 0 = new node OR -1 = offline OR 1 = online
  148. */
  149. public function getNodeOnlineStatus(): int
  150. {
  151. // 类型 9 或者心跳为 0
  152. if ($this->node_heartbeat === 0 || \in_array($this->sort, [9])) {
  153. return 0;
  154. }
  155. return $this->node_heartbeat + 300 > \time() ? 1 : -1;
  156. }
  157. /**
  158. * 获取节点速率文本信息
  159. */
  160. public function getNodeSpeedlimit(): string
  161. {
  162. if ($this->node_speedlimit === 0.0) {
  163. return '0';
  164. }
  165. if ($this->node_speedlimit >= 1024.00) {
  166. return round($this->node_speedlimit / 1024.00, 1) . 'Gbps';
  167. }
  168. return $this->node_speedlimit . 'Mbps';
  169. }
  170. /**
  171. * 节点是在线的
  172. */
  173. public function isNodeOnline(): ?bool
  174. {
  175. if ($this->node_heartbeat === 0) {
  176. return false;
  177. }
  178. return $this->node_heartbeat > \time() - 300;
  179. }
  180. /**
  181. * 节点流量已耗尽
  182. */
  183. public function isNodeTrafficOut(): bool
  184. {
  185. return ! ($this->node_bandwidth_limit === 0 || $this->node_bandwidth < $this->node_bandwidth_limit);
  186. }
  187. /**
  188. * 节点是可用的,即流量未耗尽并且在线
  189. */
  190. public function isNodeAccessable(): bool
  191. {
  192. return $this->isNodeTrafficOut() === false && $this->isNodeOnline() === true;
  193. }
  194. /**
  195. * 更新节点 IP
  196. */
  197. public function changeNodeIp(string $server_name): bool
  198. {
  199. if (! Tools::isIPv4($server_name)) {
  200. $ip = gethostbyname($server_name);
  201. if ($ip === '') {
  202. return false;
  203. }
  204. } else {
  205. $ip = $server_name;
  206. }
  207. $this->node_ip = $ip;
  208. return true;
  209. }
  210. /**
  211. * 获取节点 IP
  212. */
  213. public function getNodeIp(): string
  214. {
  215. $node_ip_str = $this->node_ip;
  216. $node_ip_array = explode(',', $node_ip_str);
  217. return $node_ip_array[0];
  218. }
  219. /**
  220. * 获取出口地址 | 用于节点IP获取的地址
  221. */
  222. public function getOutAddress(): string
  223. {
  224. return explode(';', $this->server)[0];
  225. }
  226. public function getArgs(): array
  227. {
  228. return \json_decode($this->custom_config, true);
  229. }
  230. public function setArgs(string $key, mixed $value): void
  231. {
  232. $current = \json_decode($this->custom_config);
  233. $current[$key] = $value;
  234. $this->custom_config = \json_encode($current);
  235. $this->save();
  236. }
  237. /**
  238. * 获取入口地址
  239. */
  240. public function getEntranceAddress(): string
  241. {
  242. if ($this->sort === 13) {
  243. $server = Tools::ssv2Array($this);
  244. return $server['add'];
  245. }
  246. $explode = explode(';', $this->server);
  247. $values = $this->getArgs();
  248. if (\in_array($this->sort, [0]) && isset($values['server'])) {
  249. return $values['server'];
  250. }
  251. return $explode[0];
  252. }
  253. /**
  254. * 获取偏移后的端口
  255. *
  256. * @param mixed $port
  257. */
  258. public function getOffsetPort($port)
  259. {
  260. return Tools::outPort($this->server, $this->name, $port, $this->getArgs())['port'];
  261. }
  262. /**
  263. * 获取 SS/SSR 节点
  264. */
  265. public function getItem(User $user, int $mu_port = 0, int $is_ss = 0): ?array
  266. {
  267. $node_name = $this->name;
  268. $return_array = [];
  269. if ($mu_port !== 0) {
  270. $mu_user = User::where('port', '=', $mu_port)->where('is_multi_user', '<>', 0)->first();
  271. if ($mu_user === null) {
  272. return null;
  273. }
  274. // 如果混淆和协议均为SS原生且为单端口的,即判断为AEAD单端口类型,密码配置为用户自身密码
  275. if ($mu_user->obfs === 'plain' && $mu_user->protocol === 'origin') {
  276. $mu_user->passwd = $user->passwd;
  277. $mu_user->obfs_param = '';
  278. $mu_user->protocol_param = '';
  279. } else {
  280. $mu_user->obfs_param = $user->getMuMd5();
  281. $mu_user->protocol_param = $user->id . ':' . $user->passwd;
  282. }
  283. $user = $mu_user;
  284. $node_name .= ($_ENV['disable_sub_mu_port'] ? '' : ' - ' . $mu_port . ' 单端口');
  285. }
  286. if ($is_ss) {
  287. if (! URL::SSCanConnect($user)) {
  288. return null;
  289. }
  290. $user = URL::getSSConnectInfo($user);
  291. $return_array['type'] = 'ss';
  292. } else {
  293. if (! URL::SSRCanConnect($user)) {
  294. return null;
  295. }
  296. $user = URL::getSSRConnectInfo($user);
  297. $return_array['type'] = 'ssr';
  298. }
  299. $return_array['address'] = $this->getEntranceAddress();
  300. $return_array['port'] = $user->port;
  301. $return_array['protocol'] = $user->protocol;
  302. $return_array['protocol_param'] = $user->protocol_param;
  303. $return_array['obfs'] = $user->obfs;
  304. $return_array['obfs_param'] = $user->obfs_param;
  305. if ($mu_port !== 0 && strpos($this->server, ';') !== false) {
  306. $node_tmp = Tools::outPort($this->server, $this->name, $mu_port, $this->getArgs());
  307. $return_array['port'] = $node_tmp['port'];
  308. $node_name = $node_tmp['name'];
  309. }
  310. $return_array['passwd'] = $user->passwd;
  311. $return_array['method'] = $user->method;
  312. $return_array['remark'] = $node_name;
  313. $return_array['class'] = $this->node_class;
  314. $return_array['group'] = $_ENV['appName'];
  315. $return_array['ratio'] = $this->traffic_rate;
  316. return $return_array;
  317. }
  318. /**
  319. * 获取 V2Ray 节点
  320. */
  321. public function getV2RayItem(User $user, int $mu_port = 0, int $is_ss = 0): array
  322. {
  323. $item = Tools::v2Array($this);
  324. $item['type'] = 'vmess';
  325. $item['remark'] = $this->name;
  326. $item['id'] = $user->uuid;
  327. $item['class'] = $this->node_class;
  328. return $item;
  329. }
  330. /**
  331. * 获取 V2RayPlugin | obfs 节点
  332. *
  333. * @param User $user 用户
  334. *
  335. * @return array|null
  336. */
  337. public function getV2RayPluginItem(User $user, int $mu_port = 0, int $is_ss = 0): ?array
  338. {
  339. $return_array = Tools::ssv2Array($this);
  340. // 非 AEAD 加密无法使用
  341. if ($return_array['net'] !== 'obfs' && ! \in_array($user->method, Config::getSupportParam('ss_aead_method'))) {
  342. return null;
  343. }
  344. $return_array['remark'] = $this->name;
  345. $return_array['address'] = $return_array['add'];
  346. $return_array['method'] = $user->method;
  347. $return_array['passwd'] = $user->passwd;
  348. $return_array['protocol'] = 'origin';
  349. $return_array['protocol_param'] = '';
  350. if ($return_array['net'] === 'obfs') {
  351. $return_array['obfs_param'] = $user->getMuMd5();
  352. } else {
  353. $return_array['obfs'] = 'v2ray';
  354. if ($return_array['tls'] === 'tls' && $return_array['net'] === 'ws') {
  355. $return_array['obfs_param'] = 'mode=ws;security=tls;path=' . $return_array['path'] .
  356. ';host=' . $return_array['host'];
  357. } else {
  358. $return_array['obfs_param'] = 'mode=ws;security=none;path=' . $return_array['path'] .
  359. ';host=' . $return_array['host'];
  360. }
  361. $return_array['path'] .= '?redirect=' . $user->getMuMd5();
  362. }
  363. $return_array['class'] = $this->node_class;
  364. $return_array['group'] = $_ENV['appName'];
  365. $return_array['type'] = 'ss';
  366. $return_array['ratio'] = $this->traffic_rate;
  367. return $return_array;
  368. }
  369. /**
  370. * Trojan 节点
  371. *
  372. * @param User $user 用户
  373. */
  374. public function getTrojanItem(User $user, int $mu_port = 0, int $is_ss = 0): array
  375. {
  376. $server = explode(';', $this->server);
  377. $opt = [];
  378. $item = [];
  379. $opt = $this->getArgs();
  380. $item['remark'] = $this->name;
  381. $item['type'] = 'trojan';
  382. $item['address'] = $server[0];
  383. $item['port'] = (isset($opt['offset_port_user']) ? (int) $opt['offset_port_user'] : (isset($opt['offset_port_node']) ? (int) $opt['offset_port_node'] : 443));
  384. $item['passwd'] = $user->uuid ?? '';
  385. $item['host'] = $item['address'];
  386. $item['net'] = (isset($opt['grpc']) ? 'grpc' : '');
  387. $item['servicename'] = ($opt['servicename'] ?? '');
  388. $item['flow'] = ($opt['flow'] ?? '');
  389. $xtls = ($opt['enable_xtls'] ?? '');
  390. if ($xtls === 'true') {
  391. $item['tls'] = 'xtls';
  392. } else {
  393. $item['tls'] = 'tls';
  394. }
  395. if (isset($opt['host'])) {
  396. $item['host'] = $opt['host'];
  397. }
  398. return $item;
  399. }
  400. }