NodeController.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controllers\Admin;
  4. use App\Controllers\BaseController;
  5. use App\Models\Config;
  6. use App\Models\Node;
  7. use App\Services\IM\Telegram;
  8. use App\Utils\Tools;
  9. use Psr\Http\Message\ResponseInterface;
  10. use Slim\Http\Response;
  11. use Slim\Http\ServerRequest;
  12. use SmartyException;
  13. use Telegram\Bot\Exceptions\TelegramSDKException;
  14. use function json_decode;
  15. use function json_encode;
  16. use function round;
  17. use function str_replace;
  18. use function trim;
  19. final class NodeController extends BaseController
  20. {
  21. private static array $details = [
  22. 'field' => [
  23. 'op' => '操作',
  24. 'id' => '节点ID',
  25. 'name' => '名称',
  26. 'server' => '地址',
  27. 'type' => '状态',
  28. 'sort' => '类型',
  29. 'traffic_rate' => '倍率',
  30. 'is_dynamic_rate' => '动态倍率',
  31. 'dynamic_rate_type' => '动态倍率计算方式',
  32. 'node_class' => '等级',
  33. 'node_group' => '组别',
  34. 'node_bandwidth_limit' => '流量限制/GB',
  35. 'node_bandwidth' => '已用流量/GB',
  36. 'bandwidthlimit_resetday' => '重置日',
  37. ],
  38. ];
  39. private static array $update_field = [
  40. 'name',
  41. 'server',
  42. 'traffic_rate',
  43. 'is_dynamic_rate',
  44. 'dynamic_rate_type',
  45. 'max_rate',
  46. 'max_rate_time',
  47. 'min_rate',
  48. 'min_rate_time',
  49. 'node_group',
  50. 'node_speedlimit',
  51. 'sort',
  52. 'node_class',
  53. 'node_bandwidth_limit',
  54. 'bandwidthlimit_resetday',
  55. ];
  56. /**
  57. * 后台节点页面
  58. *
  59. * @throws SmartyException
  60. */
  61. public function index(ServerRequest $request, Response $response, array $args): ResponseInterface
  62. {
  63. return $response->write(
  64. $this->view()
  65. ->assign('details', self::$details)
  66. ->fetch('admin/node/index.tpl')
  67. );
  68. }
  69. /**
  70. * 后台创建节点页面
  71. *
  72. * @throws SmartyException
  73. */
  74. public function create(ServerRequest $request, Response $response, array $args): ResponseInterface
  75. {
  76. return $response->write(
  77. $this->view()
  78. ->assign('update_field', self::$update_field)
  79. ->fetch('admin/node/create.tpl')
  80. );
  81. }
  82. /**
  83. * 后台添加节点
  84. */
  85. public function add(ServerRequest $request, Response $response, array $args): ResponseInterface
  86. {
  87. $node = new Node();
  88. $node->name = $request->getParam('name');
  89. $node->node_group = $request->getParam('node_group');
  90. $node->server = trim($request->getParam('server'));
  91. $node->traffic_rate = $request->getParam('traffic_rate') ?? 1;
  92. $node->is_dynamic_rate = $request->getParam('is_dynamic_rate') === 'true' ? 1 : 0;
  93. $node->dynamic_rate_type = $request->getParam('dynamic_rate_type') ?? 0;
  94. $node->dynamic_rate_config = json_encode([
  95. 'max_rate' => $request->getParam('max_rate') ?? 1,
  96. 'max_rate_time' => $request->getParam('max_rate_time') ?? 3,
  97. 'min_rate' => $request->getParam('min_rate') ?? 1,
  98. 'min_rate_time' => $request->getParam('min_rate_time') ?? 22,
  99. ]);
  100. $custom_config = $request->getParam('custom_config') ?? '{}';
  101. if ($custom_config !== '') {
  102. $node->custom_config = $custom_config;
  103. } else {
  104. $node->custom_config = '{}';
  105. }
  106. $node->node_speedlimit = $request->getParam('node_speedlimit');
  107. $node->type = $request->getParam('type') === 'true' ? 1 : 0;
  108. $node->sort = $request->getParam('sort');
  109. $node->node_class = $request->getParam('node_class');
  110. $node->node_bandwidth_limit = Tools::toGB($request->getParam('node_bandwidth_limit'));
  111. $node->bandwidthlimit_resetday = $request->getParam('bandwidthlimit_resetday');
  112. $node->password = Tools::genRandomChar(32);
  113. if (! $node->save()) {
  114. return $response->withJson([
  115. 'ret' => 0,
  116. 'msg' => '添加失败',
  117. ]);
  118. }
  119. if (Config::obtain('telegram_add_node')) {
  120. try {
  121. (new Telegram())->send(
  122. 0,
  123. str_replace(
  124. '%node_name%',
  125. $request->getParam('name'),
  126. Config::obtain('telegram_add_node_text')
  127. )
  128. );
  129. } catch (TelegramSDKException $e) {
  130. return $response->withJson([
  131. 'ret' => 1,
  132. 'msg' => '添加成功,但 Telegram 通知失败',
  133. 'node_id' => $node->id,
  134. ]);
  135. }
  136. }
  137. return $response->withJson([
  138. 'ret' => 1,
  139. 'msg' => '添加成功',
  140. 'node_id' => $node->id,
  141. ]);
  142. }
  143. /**
  144. * 后台编辑指定节点页面
  145. *
  146. * @throws SmartyException
  147. */
  148. public function edit(ServerRequest $request, Response $response, array $args): ResponseInterface
  149. {
  150. $node = (new Node())->find($args['id']);
  151. $dynamic_rate_config = json_decode($node->dynamic_rate_config);
  152. $node->max_rate = $dynamic_rate_config?->max_rate ?? 1;
  153. $node->max_rate_time = $dynamic_rate_config?->max_rate_time ?? 3;
  154. $node->min_rate = $dynamic_rate_config?->min_rate ?? 1;
  155. $node->min_rate_time = $dynamic_rate_config?->min_rate_time ?? 22;
  156. $node->node_bandwidth = Tools::flowToGB($node->node_bandwidth);
  157. $node->node_bandwidth_limit = Tools::flowToGB($node->node_bandwidth_limit);
  158. return $response->write(
  159. $this->view()
  160. ->assign('node', $node)
  161. ->assign('update_field', self::$update_field)
  162. ->fetch('admin/node/edit.tpl')
  163. );
  164. }
  165. /**
  166. * 后台更新指定节点内容
  167. */
  168. public function update(ServerRequest $request, Response $response, array $args): ResponseInterface
  169. {
  170. $node = (new Node())->find($args['id']);
  171. $node->name = $request->getParam('name');
  172. $node->node_group = $request->getParam('node_group') ?? 0;
  173. $node->server = trim($request->getParam('server'));
  174. $node->traffic_rate = $request->getParam('traffic_rate') ?? 1;
  175. $node->is_dynamic_rate = $request->getParam('is_dynamic_rate') === 'true' ? 1 : 0;
  176. $node->dynamic_rate_type = $request->getParam('dynamic_rate_type') ?? 0;
  177. $node->dynamic_rate_config = json_encode([
  178. 'max_rate' => $request->getParam('max_rate') ?? 1,
  179. 'max_rate_time' => $request->getParam('max_rate_time') ?? 0,
  180. 'min_rate' => $request->getParam('min_rate') ?? 1,
  181. 'min_rate_time' => $request->getParam('min_rate_time') ?? 0,
  182. ]);
  183. $custom_config = $request->getParam('custom_config') ?? '{}';
  184. if ($custom_config !== '') {
  185. $node->custom_config = $custom_config;
  186. } else {
  187. $node->custom_config = '{}';
  188. }
  189. $node->node_speedlimit = $request->getParam('node_speedlimit');
  190. $node->type = $request->getParam('type') === 'true' ? 1 : 0;
  191. $node->sort = $request->getParam('sort');
  192. $node->node_class = $request->getParam('node_class');
  193. $node->node_bandwidth_limit = Tools::toGB($request->getParam('node_bandwidth_limit'));
  194. $node->bandwidthlimit_resetday = $request->getParam('bandwidthlimit_resetday');
  195. if (! $node->save()) {
  196. return $response->withJson([
  197. 'ret' => 0,
  198. 'msg' => '修改失败',
  199. ]);
  200. }
  201. if (Config::obtain('telegram_update_node')) {
  202. try {
  203. (new Telegram())->send(
  204. 0,
  205. str_replace(
  206. '%node_name%',
  207. $request->getParam('name'),
  208. Config::obtain('telegram_update_node_text')
  209. )
  210. );
  211. } catch (TelegramSDKException $e) {
  212. return $response->withJson([
  213. 'ret' => 1,
  214. 'msg' => '修改成功,但 Telegram 通知失败',
  215. ]);
  216. }
  217. }
  218. return $response->withJson([
  219. 'ret' => 1,
  220. 'msg' => '修改成功',
  221. ]);
  222. }
  223. public function reset(ServerRequest $request, Response $response, array $args): Response|ResponseInterface
  224. {
  225. $node = (new Node())->find($args['id']);
  226. $node->password = Tools::genRandomChar(32);
  227. $node->save();
  228. return $response->withJson([
  229. 'ret' => 1,
  230. 'msg' => '重置节点通讯密钥成功',
  231. ]);
  232. }
  233. /**
  234. * 后台删除指定节点
  235. */
  236. public function delete(ServerRequest $request, Response $response, array $args): ResponseInterface
  237. {
  238. $node = (new Node())->find($args['id']);
  239. if (! $node->delete()) {
  240. return $response->withJson([
  241. 'ret' => 0,
  242. 'msg' => '删除失败',
  243. ]);
  244. }
  245. if (Config::obtain('telegram_delete_node')) {
  246. try {
  247. (new Telegram())->send(
  248. 0,
  249. str_replace(
  250. '%node_name%',
  251. $node->name,
  252. Config::obtain('telegram_delete_node_text')
  253. )
  254. );
  255. } catch (TelegramSDKException $e) {
  256. return $response->withJson([
  257. 'ret' => 1,
  258. 'msg' => '删除成功,但Telegram通知失败',
  259. ]);
  260. }
  261. }
  262. return $response->withJson([
  263. 'ret' => 1,
  264. 'msg' => '删除成功',
  265. ]);
  266. }
  267. public function copy($request, $response, $args)
  268. {
  269. $old_node = (new Node())->find($args['id']);
  270. $new_node = $old_node->replicate([
  271. 'node_bandwidth',
  272. ]);
  273. $new_node->name .= ' (副本)';
  274. $new_node->node_bandwidth = 0;
  275. if (! $new_node->save()) {
  276. return $response->withJson([
  277. 'ret' => 0,
  278. 'msg' => '复制失败',
  279. ]);
  280. }
  281. return $response->withJson([
  282. 'ret' => 1,
  283. 'msg' => '复制成功',
  284. ]);
  285. }
  286. /**
  287. * 后台节点页面 AJAX
  288. */
  289. public function ajax(ServerRequest $request, Response $response, array $args): ResponseInterface
  290. {
  291. $nodes = (new Node())->orderBy('id', 'desc')->get();
  292. foreach ($nodes as $node) {
  293. $node->op = '<button type="button" class="btn btn-red" id="delete-node-' . $node->id . '"
  294. onclick="deleteNode(' . $node->id . ')">删除</button>
  295. <button type="button" class="btn btn-orange" id="copy-node-' . $node->id . '"
  296. onclick="copyNode(' . $node->id . ')">复制</button>
  297. <a class="btn btn-blue" href="/admin/node/' . $node->id . '/edit">编辑</a>';
  298. $node->type = $node->type();
  299. $node->sort = $node->sort();
  300. $node->is_dynamic_rate = $node->isDynamicRate();
  301. $node->node_bandwidth = round(Tools::flowToGB($node->node_bandwidth), 2);
  302. $node->node_bandwidth_limit = Tools::flowToGB($node->node_bandwidth_limit);
  303. }
  304. return $response->withJson([
  305. 'nodes' => $nodes,
  306. ]);
  307. }
  308. }