mirror of
				https://github.com/v2board/v2board.git
				synced 2025-10-31 17:31:49 +08:00 
			
		
		
		
	Merge branch 'dev'
This commit is contained in:
		| @@ -8,6 +8,8 @@ use App\Services\TelegramService; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Utils\Dict; | ||||
| use App\Http\Controllers\Controller; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| use Illuminate\Support\Facades\File; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
|  | ||||
| class ConfigController extends Controller | ||||
| @@ -54,152 +56,134 @@ class ConfigController extends Controller | ||||
|  | ||||
|     public function setTelegramWebhook(Request $request) | ||||
|     { | ||||
|         $hookUrl = url('/api/v1/guest/telegram/webhook?access_token=' . md5(config('v2board.telegram_bot_token', $request->input('telegram_bot_token')))); | ||||
|         $telegramService = new TelegramService($request->input('telegram_bot_token')); | ||||
|         $telegramService->getMe(); | ||||
|         $telegramService->setWebhook( | ||||
|             url( | ||||
|                 '/api/v1/guest/telegram/webhook?access_token=' . md5(config('v2board.telegram_bot_token', $request->input('telegram_bot_token'))) | ||||
|             ) | ||||
|         ); | ||||
|         $telegramService->setWebhook($hookUrl); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function fetch() | ||||
|     public function fetch(Request $request) | ||||
|     { | ||||
|         $key = $request->input('key'); | ||||
|         $data = [ | ||||
|             'invite' => [ | ||||
|                 'invite_force' => (int)config('v2board.invite_force', 0), | ||||
|                 'invite_commission' => config('v2board.invite_commission', 10), | ||||
|                 'invite_gen_limit' => config('v2board.invite_gen_limit', 5), | ||||
|                 'invite_never_expire' => config('v2board.invite_never_expire', 0), | ||||
|                 'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1), | ||||
|                 'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1), | ||||
|                 'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100), | ||||
|                 'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT), | ||||
|                 'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0), | ||||
|                 'commission_distribution_enable' => config('v2board.commission_distribution_enable', 0), | ||||
|                 'commission_distribution_l1' => config('v2board.commission_distribution_l1'), | ||||
|                 'commission_distribution_l2' => config('v2board.commission_distribution_l2'), | ||||
|                 'commission_distribution_l3' => config('v2board.commission_distribution_l3') | ||||
|             ], | ||||
|             'site' => [ | ||||
|                 'logo' => config('v2board.logo'), | ||||
|                 'force_https' => (int)config('v2board.force_https', 0), | ||||
|                 'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0), | ||||
|                 'stop_register' => (int)config('v2board.stop_register', 0), | ||||
|                 'email_verify' => (int)config('v2board.email_verify', 0), | ||||
|                 'app_name' => config('v2board.app_name', 'V2Board'), | ||||
|                 'app_description' => config('v2board.app_description', 'V2Board is best!'), | ||||
|                 'app_url' => config('v2board.app_url'), | ||||
|                 'subscribe_url' => config('v2board.subscribe_url'), | ||||
|                 'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0), | ||||
|                 'try_out_hour' => (int)config('v2board.try_out_hour', 1), | ||||
|                 'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0), | ||||
|                 'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT), | ||||
|                 'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0), | ||||
|                 'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0), | ||||
|                 'recaptcha_key' => config('v2board.recaptcha_key'), | ||||
|                 'recaptcha_site_key' => config('v2board.recaptcha_site_key'), | ||||
|                 'tos_url' => config('v2board.tos_url'), | ||||
|                 'currency' => config('v2board.currency', 'CNY'), | ||||
|                 'currency_symbol' => config('v2board.currency_symbol', '¥'), | ||||
|                 'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0), | ||||
|                 'register_limit_count' => config('v2board.register_limit_count', 3), | ||||
|                 'register_limit_expire' => config('v2board.register_limit_expire', 60) | ||||
|             ], | ||||
|             'subscribe' => [ | ||||
|                 'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), | ||||
|                 'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0), | ||||
|                 'surplus_enable' => (int)config('v2board.surplus_enable', 1), | ||||
|                 'new_order_event_id' => (int)config('v2board.new_order_event_id', 0), | ||||
|                 'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0), | ||||
|                 'change_order_event_id' => (int)config('v2board.change_order_event_id', 0), | ||||
|                 'show_info_to_server_enable' => (int)config('v2board.show_info_to_server_enable', 0) | ||||
|             ], | ||||
|             'frontend' => [ | ||||
|                 'frontend_theme' => config('v2board.frontend_theme', 'v2board'), | ||||
|                 'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'), | ||||
|                 'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'), | ||||
|                 'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'), | ||||
|                 'frontend_background_url' => config('v2board.frontend_background_url'), | ||||
|                 'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin') | ||||
|             ], | ||||
|             'server' => [ | ||||
|                 'server_token' => config('v2board.server_token'), | ||||
|                 'server_license' => config('v2board.server_license'), | ||||
|                 'server_log_enable' => config('v2board.server_log_enable', 0), | ||||
|                 'server_v2ray_domain' => config('v2board.server_v2ray_domain'), | ||||
|                 'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'), | ||||
|             ], | ||||
|             'email' => [ | ||||
|                 'email_template' => config('v2board.email_template', 'default'), | ||||
|                 'email_host' => config('v2board.email_host'), | ||||
|                 'email_port' => config('v2board.email_port'), | ||||
|                 'email_username' => config('v2board.email_username'), | ||||
|                 'email_password' => config('v2board.email_password'), | ||||
|                 'email_encryption' => config('v2board.email_encryption'), | ||||
|                 'email_from_address' => config('v2board.email_from_address') | ||||
|             ], | ||||
|             'telegram' => [ | ||||
|                 'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0), | ||||
|                 'telegram_bot_token' => config('v2board.telegram_bot_token'), | ||||
|                 'telegram_discuss_link' => config('v2board.telegram_discuss_link') | ||||
|             ], | ||||
|             'app' => [ | ||||
|                 'windows_version' => config('v2board.windows_version'), | ||||
|                 'windows_download_url' => config('v2board.windows_download_url'), | ||||
|                 'macos_version' => config('v2board.macos_version'), | ||||
|                 'macos_download_url' => config('v2board.macos_download_url'), | ||||
|                 'android_version' => config('v2board.android_version'), | ||||
|                 'android_download_url' => config('v2board.android_download_url') | ||||
|             ] | ||||
|         ]; | ||||
|         if ($key && isset($data[$key])) { | ||||
|             return response([ | ||||
|                 'data' => [ | ||||
|                     $key => $data[$key] | ||||
|                 ] | ||||
|             ]); | ||||
|         }; | ||||
|         // TODO: default should be in Dict | ||||
|         return response([ | ||||
|             'data' => [ | ||||
|                 'invite' => [ | ||||
|                     'invite_force' => (int)config('v2board.invite_force', 0), | ||||
|                     'invite_commission' => config('v2board.invite_commission', 10), | ||||
|                     'invite_gen_limit' => config('v2board.invite_gen_limit', 5), | ||||
|                     'invite_never_expire' => config('v2board.invite_never_expire', 0), | ||||
|                     'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1), | ||||
|                     'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1), | ||||
|                     'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100), | ||||
|                     'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT), | ||||
|                     'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0), | ||||
|                     'commission_distribution_enable' => config('v2board.commission_distribution_enable', 0), | ||||
|                     'commission_distribution_l1' => config('v2board.commission_distribution_l1'), | ||||
|                     'commission_distribution_l2' => config('v2board.commission_distribution_l2'), | ||||
|                     'commission_distribution_l3' => config('v2board.commission_distribution_l3') | ||||
|                 ], | ||||
|                 'site' => [ | ||||
|                     'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0), | ||||
|                     'stop_register' => (int)config('v2board.stop_register', 0), | ||||
|                     'email_verify' => (int)config('v2board.email_verify', 0), | ||||
|                     'app_name' => config('v2board.app_name', 'V2Board'), | ||||
|                     'app_description' => config('v2board.app_description', 'V2Board is best!'), | ||||
|                     'app_url' => config('v2board.app_url'), | ||||
|                     'subscribe_url' => config('v2board.subscribe_url'), | ||||
|                     'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0), | ||||
|                     'try_out_hour' => (int)config('v2board.try_out_hour', 1), | ||||
|                     'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0), | ||||
|                     'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT), | ||||
|                     'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0), | ||||
|                     'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0), | ||||
|                     'recaptcha_key' => config('v2board.recaptcha_key'), | ||||
|                     'recaptcha_site_key' => config('v2board.recaptcha_site_key'), | ||||
|                     'tos_url' => config('v2board.tos_url'), | ||||
|                     'currency' => config('v2board.currency', 'CNY'), | ||||
|                     'currency_symbol' => config('v2board.currency_symbol', '¥') | ||||
|                 ], | ||||
|                 'subscribe' => [ | ||||
|                     'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), | ||||
|                     'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0), | ||||
|                     'surplus_enable' => (int)config('v2board.surplus_enable', 1), | ||||
|                     'new_order_event_id' => (int)config('v2board.new_order_event_id', 0), | ||||
|                     'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0), | ||||
|                     'change_order_event_id' => (int)config('v2board.change_order_event_id', 0), | ||||
|                 ], | ||||
|                 'pay' => [ | ||||
|                     // alipay | ||||
|                     'alipay_enable' => (int)config('v2board.alipay_enable'), | ||||
|                     'alipay_appid' => config('v2board.alipay_appid'), | ||||
|                     'alipay_pubkey' => config('v2board.alipay_pubkey'), | ||||
|                     'alipay_privkey' => config('v2board.alipay_privkey'), | ||||
|                     // stripe | ||||
|                     'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0), | ||||
|                     'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0), | ||||
|                     'stripe_card_enable' => (int)config('v2board.stripe_card_enable', 0), | ||||
|                     'stripe_sk_live' => config('v2board.stripe_sk_live'), | ||||
|                     'stripe_pk_live' => config('v2board.stripe_pk_live'), | ||||
|                     'stripe_webhook_key' => config('v2board.stripe_webhook_key'), | ||||
|                     'stripe_currency' => config('v2board.stripe_currency', 'hkd'), | ||||
|                     // bitpayx | ||||
|                     'bitpayx_name' => config('v2board.bitpayx_name', '在线支付'), | ||||
|                     'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0), | ||||
|                     'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'), | ||||
|                     // mGate | ||||
|                     'mgate_name' => config('v2board.mgate_name', '在线支付'), | ||||
|                     'mgate_enable' => (int)config('v2board.mgate_enable', 0), | ||||
|                     'mgate_url' => config('v2board.mgate_url'), | ||||
|                     'mgate_app_id' => config('v2board.mgate_app_id'), | ||||
|                     'mgate_app_secret' => config('v2board.mgate_app_secret'), | ||||
|                     // Epay | ||||
|                     'epay_name' => config('v2board.epay_name', '在线支付'), | ||||
|                     'epay_enable' => (int)config('v2board.epay_enable', 0), | ||||
|                     'epay_url' => config('v2board.epay_url'), | ||||
|                     'epay_pid' => config('v2board.epay_pid'), | ||||
|                     'epay_key' => config('v2board.epay_key'), | ||||
|                 ], | ||||
|                 'frontend' => [ | ||||
|                     'frontend_theme' => config('v2board.frontend_theme', 'v2board'), | ||||
|                     'frontend_theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'), | ||||
|                     'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'), | ||||
|                     'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'), | ||||
|                     'frontend_background_url' => config('v2board.frontend_background_url'), | ||||
|                     'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin'), | ||||
|                     'frontend_customer_service_method' => config('v2board.frontend_customer_service_method', 0), | ||||
|                     'frontend_customer_service_id' => config('v2board.frontend_customer_service_id'), | ||||
|                 ], | ||||
|                 'server' => [ | ||||
|                     'server_token' => config('v2board.server_token'), | ||||
|                     'server_license' => config('v2board.server_license'), | ||||
|                     'server_log_enable' => config('v2board.server_log_enable', 0), | ||||
|                     'server_v2ray_domain' => config('v2board.server_v2ray_domain'), | ||||
|                     'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'), | ||||
|                 ], | ||||
|                 'email' => [ | ||||
|                     'email_template' => config('v2board.email_template', 'default'), | ||||
|                     'email_host' => config('v2board.email_host'), | ||||
|                     'email_port' => config('v2board.email_port'), | ||||
|                     'email_username' => config('v2board.email_username'), | ||||
|                     'email_password' => config('v2board.email_password'), | ||||
|                     'email_encryption' => config('v2board.email_encryption'), | ||||
|                     'email_from_address' => config('v2board.email_from_address') | ||||
|                 ], | ||||
|                 'telegram' => [ | ||||
|                     'telegram_bot_enable' => config('v2board.telegram_bot_enable', 0), | ||||
|                     'telegram_bot_token' => config('v2board.telegram_bot_token'), | ||||
|                     'telegram_discuss_link' => config('v2board.telegram_discuss_link') | ||||
|                 ], | ||||
|                 'app' => [ | ||||
|                     'windows_version' => config('v2board.windows_version'), | ||||
|                     'windows_download_url' => config('v2board.windows_download_url'), | ||||
|                     'macos_version' => config('v2board.macos_version'), | ||||
|                     'macos_download_url' => config('v2board.macos_download_url'), | ||||
|                     'android_version' => config('v2board.android_version'), | ||||
|                     'android_download_url' => config('v2board.android_download_url') | ||||
|                 ] | ||||
|             ] | ||||
|             'data' => $data | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function save(ConfigSave $request) | ||||
|     { | ||||
|         $data = $request->validated(); | ||||
|         $array = \Config::get('v2board'); | ||||
|         foreach ($data as $k => $v) { | ||||
|             if (!in_array($k, array_keys($request->validated()))) { | ||||
|                 abort(500, '参数' . $k . '不在规则内,禁止修改'); | ||||
|         $config = config('v2board'); | ||||
|         foreach (ConfigSave::RULES as $k => $v) { | ||||
|             if (!in_array($k, array_keys(ConfigSave::RULES))) { | ||||
|                 unset($config[$k]); | ||||
|                 continue; | ||||
|             } | ||||
|             if (array_key_exists($k, $data)) { | ||||
|                 $config[$k] = $data[$k]; | ||||
|             } | ||||
|             $array[$k] = $v; | ||||
|         } | ||||
|         $data = var_export($array, 1); | ||||
|         if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) { | ||||
|         $data = var_export($config, 1); | ||||
|         if (!File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) { | ||||
|             abort(500, '修改失败'); | ||||
|         } | ||||
|         if (function_exists('opcache_reset')) { | ||||
| @@ -207,7 +191,7 @@ class ConfigController extends Controller | ||||
|                 abort(500, '缓存清除失败,请卸载或检查opcache配置状态'); | ||||
|             } | ||||
|         } | ||||
|         \Artisan::call('config:cache'); | ||||
|         Artisan::call('config:cache'); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
|   | ||||
| @@ -88,7 +88,16 @@ class CouponController extends Controller | ||||
|             array_push($coupons, $coupon); | ||||
|         } | ||||
|         DB::beginTransaction(); | ||||
|         if (!Coupon::insert($coupons)) { | ||||
|         if (!Coupon::insert(array_map(function ($item) use ($coupon) { | ||||
|             // format data | ||||
|             if (isset($item['limit_plan_ids']) && is_array($item['limit_plan_ids'])) { | ||||
|                 $item['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']); | ||||
|             } | ||||
|             if (isset($item['limit_period']) && is_array($item['limit_period'])) { | ||||
|                 $item['limit_period'] = json_encode($coupon['limit_period']); | ||||
|             } | ||||
|             return $item; | ||||
|         }, $coupons))) { | ||||
|             DB::rollBack(); | ||||
|             abort(500, '生成失败'); | ||||
|         } | ||||
|   | ||||
| @@ -22,7 +22,8 @@ class NoticeController extends Controller | ||||
|         $data = $request->only([ | ||||
|             'title', | ||||
|             'content', | ||||
|             'img_url' | ||||
|             'img_url', | ||||
|             'tags' | ||||
|         ]); | ||||
|         if (!$request->input('id')) { | ||||
|             if (!Notice::create($data)) { | ||||
|   | ||||
| @@ -46,35 +46,50 @@ class PaymentController extends Controller | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function show(Request $request) | ||||
|     { | ||||
|         $payment = Payment::find($request->input('id')); | ||||
|         if (!$payment) abort(500, '支付方式不存在'); | ||||
|         $payment->enable = !$payment->enable; | ||||
|         if (!$payment->save()) abort(500, '保存失败'); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function save(Request $request) | ||||
|     { | ||||
|         if (!config('v2board.app_url')) { | ||||
|             abort(500, '请在站点配置中配置站点地址'); | ||||
|         } | ||||
|         if ($request->input('id')) { | ||||
|             $payment = Payment::find($request->input('id')); | ||||
|             if (!$payment) abort(500, '支付方式不存在'); | ||||
|             try { | ||||
|                 $payment->update($request->input()); | ||||
|             } catch (\Exception $e) { | ||||
|                 abort(500, '更新失败'); | ||||
|             } | ||||
|             return response([ | ||||
|                 'data' => true | ||||
|             ]); | ||||
|         } | ||||
|         $params = $request->validate([ | ||||
|             'name' => 'required', | ||||
|             'icon' => 'nullable', | ||||
|             'payment' => 'required', | ||||
|             'config' => 'required', | ||||
|             'notify_domain' => 'nullable|url' | ||||
|             'notify_domain' => 'nullable|url', | ||||
|             'handling_fee_fixed' => 'nullable|integer', | ||||
|             'handling_fee_percent' => 'nullable|numeric|between:0.1,100' | ||||
|         ], [ | ||||
|             'name.required' => '显示名称不能为空', | ||||
|             'payment.required' => '网关参数不能为空', | ||||
|             'config.required' => '配置参数不能为空', | ||||
|             'notify_domain.url' => '自定义通知域名格式有误' | ||||
|             'notify_domain.url' => '自定义通知域名格式有误', | ||||
|             'handling_fee_fixed.integer' => '固定手续费格式有误', | ||||
|             'handling_fee_percent.between' => '百分比手续费范围须在0.1-100之间' | ||||
|         ]); | ||||
|         if ($request->input('id')) { | ||||
|             $payment = Payment::find($request->input('id')); | ||||
|             if (!$payment) abort(500, '支付方式不存在'); | ||||
|             try { | ||||
|                 $payment->update($params); | ||||
|             } catch (\Exception $e) { | ||||
|                 abort(500, $e->getMessage()); | ||||
|             } | ||||
|             return response([ | ||||
|                 'data' => true | ||||
|             ]); | ||||
|         } | ||||
|         $params['uuid'] = Helper::randomChar(8); | ||||
|         if (!Payment::create($params)) { | ||||
|             abort(500, '保存失败'); | ||||
|   | ||||
| @@ -16,7 +16,6 @@ class PlanController extends Controller | ||||
| { | ||||
|     public function fetch(Request $request) | ||||
|     { | ||||
|  | ||||
|         $counts = User::select( | ||||
|             DB::raw("plan_id"), | ||||
|             DB::raw("count(*) as count") | ||||
|   | ||||
| @@ -71,12 +71,12 @@ class StatController extends Controller | ||||
|                 'value' => $statistic['order_count'] | ||||
|             ]); | ||||
|             array_push($result, [ | ||||
|                 'type' => '佣金金额', | ||||
|                 'type' => '佣金金额(已发放)', | ||||
|                 'date' => $date, | ||||
|                 'value' => $statistic['commission_amount'] / 100 | ||||
|             ]); | ||||
|             array_push($result, [ | ||||
|                 'type' => '佣金笔数', | ||||
|                 'type' => '佣金笔数(已发放)', | ||||
|                 'date' => $date, | ||||
|                 'value' => $statistic['commission_count'] | ||||
|             ]); | ||||
| @@ -94,7 +94,8 @@ class StatController extends Controller | ||||
|             'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(), | ||||
|             'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray() | ||||
|         ]; | ||||
|         $timestamp = strtotime('-1 day', strtotime(date('Y-m-d'))); | ||||
|         $startAt = strtotime('-1 day', strtotime(date('Y-m-d'))); | ||||
|         $endAt = strtotime(date('Y-m-d')); | ||||
|         $statistics = StatServer::select([ | ||||
|                 'server_id', | ||||
|                 'server_type', | ||||
| @@ -102,7 +103,8 @@ class StatController extends Controller | ||||
|                 'd', | ||||
|                 DB::raw('(u+d) as total') | ||||
|             ]) | ||||
|             ->where('record_at', '>=', $timestamp) | ||||
|             ->where('record_at', '>=', $startAt) | ||||
|             ->where('record_at', '<', $endAt) | ||||
|             ->where('record_type', 'd') | ||||
|             ->limit(10) | ||||
|             ->orderBy('total', 'DESC') | ||||
|   | ||||
							
								
								
									
										90
									
								
								app/Http/Controllers/Admin/ThemeController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								app/Http/Controllers/Admin/ThemeController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\Http\Controllers\Admin; | ||||
|  | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Services\ThemeService; | ||||
| use Illuminate\Support\Facades\File; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| use Illuminate\Http\Request; | ||||
|  | ||||
| class ThemeController extends Controller | ||||
| { | ||||
|     private $themes; | ||||
|     private $path; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->path = $path = public_path('theme/'); | ||||
|         $this->themes = array_map(function ($item) use ($path) { | ||||
|             return str_replace($path, '', $item); | ||||
|         }, glob($path . '*')); | ||||
|     } | ||||
|  | ||||
|     public function getThemes() | ||||
|     { | ||||
|         $themeConfigs = []; | ||||
|         foreach ($this->themes as $theme) { | ||||
|             $themeConfigFile = $this->path . "{$theme}/config.php"; | ||||
|             if (!File::exists($themeConfigFile)) continue; | ||||
|             $themeConfig = include($themeConfigFile); | ||||
|             if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue; | ||||
|             $themeConfigs[$theme] = $themeConfig; | ||||
|             if (config("theme.{$theme}")) continue; | ||||
|             $themeService = new ThemeService($theme); | ||||
|             $themeService->init(); | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => [ | ||||
|                 'themes' => $themeConfigs, | ||||
|                 'active' => config('v2board.frontend_theme', 'v2board') | ||||
|             ] | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function getThemeConfig(Request $request) | ||||
|     { | ||||
|         $payload = $request->validate([ | ||||
|             'name' => 'required|in:' . join(',', $this->themes) | ||||
|         ]); | ||||
|         return response([ | ||||
|             'data' => config("theme.{$payload['name']}") | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     public function saveThemeConfig(Request $request) | ||||
|     { | ||||
|         $payload = $request->validate([ | ||||
|             'name' => 'required|in:' . join(',', $this->themes), | ||||
|             'config' => 'required' | ||||
|         ]); | ||||
|         $payload['config'] = json_decode(base64_decode($payload['config']), true); | ||||
|         if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误'); | ||||
|         $themeConfigFile = public_path("theme/{$payload['name']}/config.php"); | ||||
|         if (!File::exists($themeConfigFile)) abort(500, '主题不存在'); | ||||
|         $themeConfig = include($themeConfigFile); | ||||
|         $validateFields = array_column($themeConfig['configs'], 'field_name'); | ||||
|         $config = []; | ||||
|         foreach ($validateFields as $validateField) { | ||||
|             $config[$validateField] = isset($payload['config'][$validateField]) ? $payload['config'][$validateField] : ''; | ||||
|         } | ||||
|  | ||||
|         File::ensureDirectoryExists(base_path() . '/config/theme/'); | ||||
|  | ||||
|         $data = var_export($config, 1); | ||||
|         if (!File::put(base_path() . "/config/theme/{$payload['name']}.php", "<?php\n return $data ;")) { | ||||
|             abort(500, '修改失败'); | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             Artisan::call('config:cache'); | ||||
| //            sleep(2); | ||||
|         } catch (\Exception $e) { | ||||
|             abort(500, '保存失败'); | ||||
|         } | ||||
|  | ||||
|         return response([ | ||||
|             'data' => $config | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| @@ -36,20 +36,20 @@ class TicketController extends Controller | ||||
|         } | ||||
|         $current = $request->input('current') ? $request->input('current') : 1; | ||||
|         $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; | ||||
|         $model = Ticket::orderBy('created_at', 'DESC'); | ||||
|         $model = Ticket::orderBy('updated_at', 'DESC'); | ||||
|         if ($request->input('status') !== NULL) { | ||||
|             $model->where('status', $request->input('status')); | ||||
|         } | ||||
|         if ($request->input('reply_status') !== NULL) { | ||||
|             $model->whereIn('reply_status', $request->input('reply_status')); | ||||
|         } | ||||
|         if ($request->input('email') !== NULL) { | ||||
|             $user = User::where('email', $request->input('email'))->first(); | ||||
|             if ($user) $model->where('user_id', $user->id); | ||||
|         } | ||||
|         $total = $model->count(); | ||||
|         $res = $model->forPage($current, $pageSize) | ||||
|             ->get(); | ||||
|         for ($i = 0; $i < count($res); $i++) { | ||||
|             if ($res[$i]['last_reply_user_id'] == $request->session()->get('id')) { | ||||
|                 $res[$i]['reply_status'] = 0; | ||||
|             } else { | ||||
|                 $res[$i]['reply_status'] = 1; | ||||
|             } | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => $res, | ||||
|             'total' => $total | ||||
|   | ||||
| @@ -74,7 +74,7 @@ class UserController extends Controller | ||||
|                     $res[$i]['plan_name'] = $plan[$k]['name']; | ||||
|                 } | ||||
|             } | ||||
|             $res[$i]['subscribe_url'] = Helper::getSubscribeHost() . '/api/v1/client/subscribe?token=' . $res[$i]['token']; | ||||
|             $res[$i]['subscribe_url'] = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $res[$i]['token']); | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => $res, | ||||
| @@ -153,7 +153,6 @@ class UserController extends Controller | ||||
|         } | ||||
|  | ||||
|         $data = "邮箱,余额,推广佣金,总流量,剩余流量,套餐到期时间,订阅计划,订阅地址\r\n"; | ||||
|         $baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))); | ||||
|         foreach($res as $user) { | ||||
|             $expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']); | ||||
|             $balance = $user['balance'] / 100; | ||||
| @@ -161,7 +160,7 @@ class UserController extends Controller | ||||
|             $transferEnable = $user['transfer_enable'] ? $user['transfer_enable'] / 1073741824 : 0; | ||||
|             $notUseFlow = (($user['transfer_enable'] - ($user['u'] + $user['d'])) / 1073741824) ?? 0; | ||||
|             $planName = $user['plan_name'] ?? '无订阅'; | ||||
|             $subscribeUrl = $baseUrl . '/api/v1/client/subscribe?token=' . $user['token']; | ||||
|             $subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']); | ||||
|             $data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n"; | ||||
|         } | ||||
|         echo "\xEF\xBB\xBF" . $data; | ||||
| @@ -232,12 +231,11 @@ class UserController extends Controller | ||||
|         } | ||||
|         DB::commit(); | ||||
|         $data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n"; | ||||
|         $baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))); | ||||
|         foreach($users as $user) { | ||||
|             $expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']); | ||||
|             $createDate = date('Y-m-d H:i:s', $user['created_at']); | ||||
|             $password = $request->input('password') ?? $user['email']; | ||||
|             $subscribeUrl = $baseUrl . '/api/v1/client/subscribe?token=' . $user['token']; | ||||
|             $subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']); | ||||
|             $data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n"; | ||||
|         } | ||||
|         echo $data; | ||||
|   | ||||
| @@ -23,6 +23,7 @@ class ClientController extends Controller | ||||
|         if ($userService->isAvailable($user)) { | ||||
|             $serverService = new ServerService(); | ||||
|             $servers = $serverService->getAvailableServers($user); | ||||
|             $this->setSubscribeInfoToServers($servers, $user); | ||||
|             if ($flag) { | ||||
|                 foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) { | ||||
|                     $file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php'); | ||||
| @@ -38,4 +39,26 @@ class ClientController extends Controller | ||||
|             die('该客户端暂不支持进行订阅'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function setSubscribeInfoToServers(&$servers, $user) | ||||
|     { | ||||
|         if (!(int)config('v2board.show_info_to_server_enable', 0)) return; | ||||
|         $useTraffic = round($user['u'] / (1024*1024*1024), 2) + round($user['d'] / (1024*1024*1024), 2); | ||||
|         $totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2); | ||||
|         $remainingTraffic = $totalTraffic - $useTraffic; | ||||
|         $expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : '长期有效'; | ||||
|         $userService = new UserService(); | ||||
|         $resetDay = $userService->getResetDay($user); | ||||
|         array_unshift($servers, array_merge($servers[0], [ | ||||
|             'name' => "套餐到期:{$expiredDate}", | ||||
|         ])); | ||||
|         if ($resetDay) { | ||||
|             array_unshift($servers, array_merge($servers[0], [ | ||||
|                 'name' => "距离下次重置剩余:{$resetDay} 天", | ||||
|             ])); | ||||
|         } | ||||
|         array_unshift($servers, array_merge($servers[0], [ | ||||
|             'name' => "剩余流量:{$remainingTraffic} GB", | ||||
|         ])); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class Clash | ||||
|         $appName = config('v2board.app_name', 'V2Board'); | ||||
|         header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); | ||||
|         header('profile-update-interval: 24'); | ||||
|         header("content-disposition:attachment;filename={$appName}"); | ||||
|         header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName)); | ||||
|         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml'; | ||||
|         $customConfig = base_path() . '/resources/rules/custom.clash.yaml'; | ||||
|         if (\File::exists($customConfig)) { | ||||
| @@ -133,7 +133,7 @@ class Clash | ||||
|             if ($server['networkSettings']) { | ||||
|                 $grpcSettings = $server['networkSettings']; | ||||
|                 $array['grpc-opts'] = []; | ||||
|                 $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName']; | ||||
|                 if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName']; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,9 +2,9 @@ | ||||
| 
 | ||||
| namespace App\Http\Controllers\Client\Protocols; | ||||
| 
 | ||||
| class AnXray | ||||
| class SagerNet | ||||
| { | ||||
|     public $flag = 'axxray'; | ||||
|     public $flag = 'sagernet'; | ||||
|     private $servers; | ||||
|     private $user; | ||||
| 
 | ||||
| @@ -74,7 +74,7 @@ class AnXray | ||||
|         } | ||||
|         if ((string)$server['network'] === 'ws') { | ||||
|             $wsSettings = $server['networkSettings']; | ||||
|             if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']); | ||||
|             if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; | ||||
|             if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']); | ||||
|         } | ||||
|         if ((string)$server['network'] === 'grpc') { | ||||
| @@ -23,7 +23,7 @@ class Stash | ||||
|         $appName = config('v2board.app_name', 'V2Board'); | ||||
|         header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); | ||||
|         header('profile-update-interval: 24'); | ||||
|         header("content-disposition: filename={$appName}"); | ||||
|         header("content-disposition: filename*=UTF-8''".rawurlencode($appName)); | ||||
|         // 暂时使用clash配置文件,后续根据Stash更新情况更新 | ||||
|         $defaultConfig = base_path() . '/resources/rules/default.clash.yaml'; | ||||
|         $customConfig = base_path() . '/resources/rules/custom.clash.yaml'; | ||||
| @@ -134,7 +134,7 @@ class Stash | ||||
|             if ($server['networkSettings']) { | ||||
|                 $grpcSettings = $server['networkSettings']; | ||||
|                 $array['grpc-opts'] = []; | ||||
|                 $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName']; | ||||
|                 if (isset($grpcSettings['serviceName']))  $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName']; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| namespace App\Http\Controllers\Client\Protocols; | ||||
|  | ||||
| use App\Utils\Helper; | ||||
|  | ||||
| class Surfboard | ||||
| { | ||||
| @@ -53,7 +54,7 @@ class Surfboard | ||||
|         } | ||||
|  | ||||
|         // Subscription link | ||||
|         $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']; | ||||
|         $subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); | ||||
|         $subsDomain = $_SERVER['SERVER_NAME']; | ||||
|  | ||||
|         $config = str_replace('$subs_link', $subsURL, $config); | ||||
|   | ||||
| @@ -2,6 +2,8 @@ | ||||
|  | ||||
| namespace App\Http\Controllers\Client\Protocols; | ||||
|  | ||||
| use App\Utils\Helper; | ||||
|  | ||||
| class Surge | ||||
| { | ||||
|     public $flag = 'surge'; | ||||
| @@ -52,6 +54,7 @@ class Surge | ||||
|         } | ||||
|  | ||||
|         // Subscription link | ||||
|         $subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); | ||||
|         $subsDomain = $_SERVER['SERVER_NAME']; | ||||
|         $subsURL = 'https://' . $subsDomain . '/api/v1/client/subscribe?token=' . $user['token']; | ||||
|  | ||||
|   | ||||
| @@ -21,7 +21,8 @@ class CommController extends Controller | ||||
|                 'is_recaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0, | ||||
|                 'recaptcha_site_key' => config('v2board.recaptcha_site_key'), | ||||
|                 'app_description' => config('v2board.app_description'), | ||||
|                 'app_url' => config('v2board.app_url') | ||||
|                 'app_url' => config('v2board.app_url'), | ||||
|                 'logo' => config('v2board.logo'), | ||||
|             ] | ||||
|         ]); | ||||
|     } | ||||
| @@ -34,11 +35,4 @@ class CommController extends Controller | ||||
|         } | ||||
|         return $suffix; | ||||
|     } | ||||
|  | ||||
|     public function getHitokoto() | ||||
|     { | ||||
|         return response([ | ||||
|             'data' => Http::get('https://v1.hitokoto.cn/')->json() | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class PaymentController extends Controller | ||||
|         if (!$order) { | ||||
|             abort(500, 'order is not found'); | ||||
|         } | ||||
|         if ($order->status === 1) return true; | ||||
|         if ($order->status !== 0) return true; | ||||
|         $orderService = new OrderService($order); | ||||
|         if (!$orderService->paid($callbackNo)) { | ||||
|             return false; | ||||
|   | ||||
| @@ -20,6 +20,12 @@ class AuthController extends Controller | ||||
| { | ||||
|     public function register(AuthRegister $request) | ||||
|     { | ||||
|         if ((int)config('v2board.register_limit_by_ip_enable', 0)) { | ||||
|             $registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0; | ||||
|             if ((int)$registerCountByIP >= (int)config('v2board.register_limit_count', 3)) { | ||||
|                 abort(500, __('Register frequently, please try again after 1 hour')); | ||||
|             } | ||||
|         } | ||||
|         if ((int)config('v2board.recaptcha_enable', 0)) { | ||||
|             $recaptcha = new ReCaptcha(config('v2board.recaptcha_key')); | ||||
|             $recaptchaResp = $recaptcha->verify($request->input('recaptcha_data')); | ||||
| @@ -109,6 +115,16 @@ class AuthController extends Controller | ||||
|         ]; | ||||
|         $request->session()->put('email', $user->email); | ||||
|         $request->session()->put('id', $user->id); | ||||
|         $user->last_login_at = time(); | ||||
|         $user->save(); | ||||
|  | ||||
|         if ((int)config('v2board.register_limit_by_ip_enable', 0)) { | ||||
|             Cache::put( | ||||
|                 CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()), | ||||
|                 (int)$registerCountByIP + 1, | ||||
|                 (int)config('v2board.register_limit_expire', 60) * 60 | ||||
|             ); | ||||
|         } | ||||
|         return response()->json([ | ||||
|             'data' => $data | ||||
|         ]); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ use Illuminate\Support\Facades\Cache; | ||||
|  */ | ||||
| class DeepbworkController extends Controller | ||||
| { | ||||
|     CONST V2RAY_CONFIG = '{"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbounds":[{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},{"listen":"127.0.0.1","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"outbounds":[{"protocol":"freedom","settings":{}},{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"type":"field","inboundTag":"api","outboundTag":"api"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}'; | ||||
|     public function __construct(Request $request) | ||||
|     { | ||||
|         $token = $request->input('token'); | ||||
| @@ -52,13 +53,16 @@ class DeepbworkController extends Controller | ||||
|                 "level" => 0, | ||||
|             ]; | ||||
|             unset($user['uuid']); | ||||
|             unset($user['email']); | ||||
|             array_push($result, $user); | ||||
|         } | ||||
|         $eTag = sha1(json_encode($result)); | ||||
|         if (strpos($request->header('If-None-Match'), $eTag) !== false ) { | ||||
|             abort(304); | ||||
|         } | ||||
|         return response([ | ||||
|             'msg' => 'ok', | ||||
|             'data' => $result, | ||||
|         ]); | ||||
|         ])->header('ETag', "\"{$eTag}\""); | ||||
|     } | ||||
|  | ||||
|     // 后端提交数据 | ||||
| @@ -97,13 +101,133 @@ class DeepbworkController extends Controller | ||||
|         if (empty($nodeId) || empty($localPort)) { | ||||
|             abort(500, '参数错误'); | ||||
|         } | ||||
|         $serverService = new ServerService(); | ||||
|         try { | ||||
|             $json = $serverService->getV2RayConfig($nodeId, $localPort); | ||||
|             $json = $this->getV2RayConfig($nodeId, $localPort); | ||||
|         } catch (\Exception $e) { | ||||
|             abort(500, $e->getMessage()); | ||||
|         } | ||||
|  | ||||
|         die(json_encode($json, JSON_UNESCAPED_UNICODE)); | ||||
|     } | ||||
|  | ||||
|     private function getV2RayConfig(int $nodeId, int $localPort) | ||||
|     { | ||||
|         $server = ServerV2ray::find($nodeId); | ||||
|         if (!$server) { | ||||
|             abort(500, '节点不存在'); | ||||
|         } | ||||
|         $json = json_decode(self::V2RAY_CONFIG); | ||||
|         $json->log->loglevel = (int)config('v2board.server_log_enable') ? 'debug' : 'none'; | ||||
|         $json->inbounds[1]->port = (int)$localPort; | ||||
|         $json->inbounds[0]->port = (int)$server->server_port; | ||||
|         $json->inbounds[0]->streamSettings->network = $server->network; | ||||
|         $this->setDns($server, $json); | ||||
|         $this->setNetwork($server, $json); | ||||
|         $this->setRule($server, $json); | ||||
|         $this->setTls($server, $json); | ||||
|  | ||||
|         return $json; | ||||
|     } | ||||
|  | ||||
|     private function setDns(ServerV2ray $server, object $json) | ||||
|     { | ||||
|         if ($server->dnsSettings) { | ||||
|             $dns = $server->dnsSettings; | ||||
|             if (isset($dns->servers)) { | ||||
|                 array_push($dns->servers, '1.1.1.1'); | ||||
|                 array_push($dns->servers, 'localhost'); | ||||
|             } | ||||
|             $json->dns = $dns; | ||||
|             $json->outbounds[0]->settings->domainStrategy = 'UseIP'; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function setNetwork(ServerV2ray $server, object $json) | ||||
|     { | ||||
|         if ($server->networkSettings) { | ||||
|             switch ($server->network) { | ||||
|                 case 'tcp': | ||||
|                     $json->inbounds[0]->streamSettings->tcpSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'kcp': | ||||
|                     $json->inbounds[0]->streamSettings->kcpSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'ws': | ||||
|                     $json->inbounds[0]->streamSettings->wsSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'http': | ||||
|                     $json->inbounds[0]->streamSettings->httpSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'domainsocket': | ||||
|                     $json->inbounds[0]->streamSettings->dsSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'quic': | ||||
|                     $json->inbounds[0]->streamSettings->quicSettings = $server->networkSettings; | ||||
|                     break; | ||||
|                 case 'grpc': | ||||
|                     $json->inbounds[0]->streamSettings->grpcSettings = $server->networkSettings; | ||||
|                     break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function setRule(ServerV2ray $server, object $json) | ||||
|     { | ||||
|         $domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain'))); | ||||
|         $protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol'))); | ||||
|         if ($server->ruleSettings) { | ||||
|             $ruleSettings = $server->ruleSettings; | ||||
|             // domain | ||||
|             if (isset($ruleSettings->domain)) { | ||||
|                 $ruleSettings->domain = array_filter($ruleSettings->domain); | ||||
|                 if (!empty($ruleSettings->domain)) { | ||||
|                     $domainRules = array_merge($domainRules, $ruleSettings->domain); | ||||
|                 } | ||||
|             } | ||||
|             // protocol | ||||
|             if (isset($ruleSettings->protocol)) { | ||||
|                 $ruleSettings->protocol = array_filter($ruleSettings->protocol); | ||||
|                 if (!empty($ruleSettings->protocol)) { | ||||
|                     $protocolRules = array_merge($protocolRules, $ruleSettings->protocol); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (!empty($domainRules)) { | ||||
|             $domainObj = new \StdClass(); | ||||
|             $domainObj->type = 'field'; | ||||
|             $domainObj->domain = $domainRules; | ||||
|             $domainObj->outboundTag = 'block'; | ||||
|             array_push($json->routing->rules, $domainObj); | ||||
|         } | ||||
|         if (!empty($protocolRules)) { | ||||
|             $protocolObj = new \StdClass(); | ||||
|             $protocolObj->type = 'field'; | ||||
|             $protocolObj->protocol = $protocolRules; | ||||
|             $protocolObj->outboundTag = 'block'; | ||||
|             array_push($json->routing->rules, $protocolObj); | ||||
|         } | ||||
|         if (empty($domainRules) && empty($protocolRules)) { | ||||
|             $json->inbounds[0]->sniffing->enabled = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private function setTls(ServerV2ray $server, object $json) | ||||
|     { | ||||
|         if ((int)$server->tls) { | ||||
|             $tlsSettings = $server->tlsSettings; | ||||
|             $json->inbounds[0]->streamSettings->security = 'tls'; | ||||
|             $tls = (object)[ | ||||
|                 'certificateFile' => '/root/.cert/server.crt', | ||||
|                 'keyFile' => '/root/.cert/server.key' | ||||
|             ]; | ||||
|             $json->inbounds[0]->streamSettings->tlsSettings = new \StdClass(); | ||||
|             if (isset($tlsSettings->serverName)) { | ||||
|                 $json->inbounds[0]->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName; | ||||
|             } | ||||
|             if (isset($tlsSettings->allowInsecure)) { | ||||
|                 $json->inbounds[0]->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false; | ||||
|             } | ||||
|             $json->inbounds[0]->streamSettings->tlsSettings->certificates[0] = $tls; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -48,9 +48,13 @@ class ShadowsocksTidalabController extends Controller | ||||
|                 'secret' => $user->uuid | ||||
|             ]); | ||||
|         } | ||||
|         $eTag = sha1(json_encode($result)); | ||||
|         if (strpos($request->header('If-None-Match'), $eTag) !== false ) { | ||||
|             abort(304); | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => $result | ||||
|         ]); | ||||
|         ])->header('ETag', "\"{$eTag}\""); | ||||
|     } | ||||
|  | ||||
|     // 后端提交数据 | ||||
|   | ||||
| @@ -20,6 +20,7 @@ use Illuminate\Support\Facades\Cache; | ||||
|  */ | ||||
| class TrojanTidalabController extends Controller | ||||
| { | ||||
|     CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}'; | ||||
|     public function __construct(Request $request) | ||||
|     { | ||||
|         $token = $request->input('token'); | ||||
| @@ -49,13 +50,16 @@ class TrojanTidalabController extends Controller | ||||
|                 "password" => $user->uuid, | ||||
|             ]; | ||||
|             unset($user['uuid']); | ||||
|             unset($user['email']); | ||||
|             array_push($result, $user); | ||||
|         } | ||||
|         $eTag = sha1(json_encode($result)); | ||||
|         if (strpos($request->header('If-None-Match'), $eTag) !== false ) { | ||||
|             abort(304); | ||||
|         } | ||||
|         return response([ | ||||
|             'msg' => 'ok', | ||||
|             'data' => $result, | ||||
|         ]); | ||||
|         ])->header('ETag', "\"{$eTag}\""); | ||||
|     } | ||||
|  | ||||
|     // 后端提交数据 | ||||
| @@ -94,13 +98,28 @@ class TrojanTidalabController extends Controller | ||||
|         if (empty($nodeId) || empty($localPort)) { | ||||
|             abort(500, '参数错误'); | ||||
|         } | ||||
|         $serverService = new ServerService(); | ||||
|         try { | ||||
|             $json = $serverService->getTrojanConfig($nodeId, $localPort); | ||||
|             $json = $this->getTrojanConfig($nodeId, $localPort); | ||||
|         } catch (\Exception $e) { | ||||
|             abort(500, $e->getMessage()); | ||||
|         } | ||||
|  | ||||
|         die(json_encode($json, JSON_UNESCAPED_UNICODE)); | ||||
|     } | ||||
|  | ||||
|     private function getTrojanConfig(int $nodeId, int $localPort) | ||||
|     { | ||||
|         $server = ServerTrojan::find($nodeId); | ||||
|         if (!$server) { | ||||
|             abort(500, '节点不存在'); | ||||
|         } | ||||
|  | ||||
|         $json = json_decode(self::TROJAN_CONFIG); | ||||
|         $json->local_port = $server->server_port; | ||||
|         $json->ssl->sni = $server->server_name ? $server->server_name : $server->host; | ||||
|         $json->ssl->cert = "/root/.cert/server.crt"; | ||||
|         $json->ssl->key = "/root/.cert/server.key"; | ||||
|         $json->api->api_port = $localPort; | ||||
|         return $json; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										128
									
								
								app/Http/Controllers/Server/VProxyController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								app/Http/Controllers/Server/VProxyController.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| <?php | ||||
|  | ||||
| namespace App\Http\Controllers\Server; | ||||
|  | ||||
| use App\Services\ServerService; | ||||
| use App\Services\UserService; | ||||
| use App\Utils\CacheKey; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Models\ServerShadowsocks; | ||||
| use App\Models\ServerV2ray; | ||||
| use App\Models\ServerTrojan; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
|  | ||||
| class VProxyController extends Controller | ||||
| { | ||||
|     private $nodeType; | ||||
|     private $nodeInfo; | ||||
|     private $nodeId; | ||||
|     private $token; | ||||
|  | ||||
|     public function __construct(Request $request) | ||||
|     { | ||||
|         $token = $request->input('token'); | ||||
|         if (empty($token)) { | ||||
|             abort(500, 'token is null'); | ||||
|         } | ||||
|         if ($token !== config('v2board.server_token')) { | ||||
|             abort(500, 'token is error'); | ||||
|         } | ||||
|         $this->token = $token; | ||||
|         $this->nodeType = $request->input('node_type'); | ||||
|         $this->nodeId = $request->input('node_id'); | ||||
|         switch ($this->nodeType) { | ||||
|             case 'v2ray': | ||||
|                 $this->nodeInfo = ServerV2ray::find($this->nodeId); | ||||
|                 break; | ||||
|             case 'shadowsocks': | ||||
|                 $this->nodeInfo = ServerShadowsocks::find($this->nodeId); | ||||
|                 break; | ||||
|             case 'trojan': | ||||
|                 $this->nodeInfo = ServerTrojan::find($this->nodeId); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|         if (!$this->nodeInfo) { | ||||
|             abort(500, 'server not found'); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // 后端获取用户 | ||||
|     public function user(Request $request) | ||||
|     { | ||||
|         ini_set('memory_limit', -1); | ||||
|         Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600); | ||||
|         $serverService = new ServerService(); | ||||
|         $users = $serverService->getAvailableUsers($this->nodeInfo->group_id); | ||||
|         $users = $users->toArray(); | ||||
|  | ||||
|         $response['users'] = $users; | ||||
|  | ||||
|         switch ($this->nodeType) { | ||||
|             case 'shadowsocks': | ||||
|                 $response['server'] = [ | ||||
|                     'cipher' => $this->nodeInfo->cipher, | ||||
|                     'server_port' => $this->nodeInfo->server_port | ||||
|                 ]; | ||||
|                 break; | ||||
|         } | ||||
|  | ||||
|         $eTag = sha1(json_encode($response)); | ||||
|         if (strpos($request->header('If-None-Match'), $eTag) !== false ) { | ||||
|             abort(304); | ||||
|         } | ||||
|  | ||||
|         return response($response)->header('ETag', "\"{$eTag}\""); | ||||
|     } | ||||
|  | ||||
|     // 后端提交数据 | ||||
|     public function submit(Request $request) | ||||
|     { | ||||
|         $data = file_get_contents('php://input'); | ||||
|         $data = json_decode($data, true); | ||||
|         Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), count($data), 3600); | ||||
|         Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600); | ||||
|         $userService = new UserService(); | ||||
|         foreach ($data as $item) { | ||||
|             $u = $item['u'] * $this->nodeInfo->rate; | ||||
|             $d = $item['d'] * $this->nodeInfo->rate; | ||||
|             $userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo, $this->nodeType); | ||||
|         } | ||||
|  | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     // 后端获取配置 | ||||
|     public function config(Request $request) | ||||
|     { | ||||
|         switch ($this->nodeType) { | ||||
|             case 'shadowsocks': | ||||
|                 die(json_encode([ | ||||
|                     'server_port' => $this->nodeInfo->server_port, | ||||
|                     'cipher' => $this->nodeInfo->cipher, | ||||
|                     'obfs' => $this->nodeInfo->obfs, | ||||
|                     'obfs_settings' => $this->nodeInfo->obfs_settings | ||||
|                 ], JSON_UNESCAPED_UNICODE)); | ||||
|                 break; | ||||
|             case 'v2ray': | ||||
|                 die(json_encode([ | ||||
|                     'server_port' => $this->nodeInfo->server_port, | ||||
|                     'network' => $this->nodeInfo->network, | ||||
|                     'cipher' => $this->nodeInfo->cipher, | ||||
|                     'networkSettings' => $this->nodeInfo->networkSettings, | ||||
|                     'tls' => $this->nodeInfo->tls | ||||
|                 ], JSON_UNESCAPED_UNICODE)); | ||||
|                 break; | ||||
|             case 'trojan': | ||||
|                 die(json_encode([ | ||||
|                     'host' => $this->nodeInfo->host, | ||||
|                     'server_port' => $this->nodeInfo->server_port | ||||
|                 ], JSON_UNESCAPED_UNICODE)); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -39,13 +39,6 @@ class TicketController extends Controller | ||||
|         $total = $model->count(); | ||||
|         $res = $model->forPage($current, $pageSize) | ||||
|             ->get(); | ||||
|         for ($i = 0; $i < count($res); $i++) { | ||||
|             if ($res[$i]['last_reply_user_id'] == $request->session()->get('id')) { | ||||
|                 $res[$i]['reply_status'] = 0; | ||||
|             } else { | ||||
|                 $res[$i]['reply_status'] = 1; | ||||
|             } | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => $res, | ||||
|             'total' => $total | ||||
|   | ||||
| @@ -29,6 +29,7 @@ class InviteController extends Controller | ||||
|     { | ||||
|         return response([ | ||||
|             'data' => CommissionLog::where('invite_user_id', $request->session()->get('id')) | ||||
|                 ->where('get_amount', '>', 0) | ||||
|                 ->select([ | ||||
|                     'id', | ||||
|                     'trade_no', | ||||
|   | ||||
| @@ -5,6 +5,7 @@ namespace App\Http\Controllers\User; | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Models\User; | ||||
| use App\Services\UserService; | ||||
| use App\Utils\Helper; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Knowledge; | ||||
|  | ||||
| @@ -28,12 +29,7 @@ class KnowledgeController extends Controller | ||||
|                 $appleIdPassword = __('No active subscription. Unable to use our provided Apple ID'); | ||||
|                 $this->formatAccessData($knowledge['body']); | ||||
|             } | ||||
|             $subscribeUrl = config('v2board.app_url', env('APP_URL')); | ||||
|             $subscribeUrls = explode(',', config('v2board.subscribe_url')); | ||||
|             if ($subscribeUrls) { | ||||
|                 $subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)]; | ||||
|             } | ||||
|             $subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}"; | ||||
|             $subscribeUrl = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); | ||||
|             $knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']); | ||||
|             $knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']); | ||||
|             $knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']); | ||||
| @@ -63,10 +59,12 @@ class KnowledgeController extends Controller | ||||
|  | ||||
|     private function formatAccessData(&$body) | ||||
|     { | ||||
|         function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $substr;} | ||||
|         $accessData = getBetween($body, '<!--access start-->', '<!--access end-->'); | ||||
|         if ($accessData) { | ||||
|             $body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body); | ||||
|         function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $start . $substr . $end;} | ||||
|         while (strpos($body, '<!--access start-->') !== false) { | ||||
|             $accessData = getBetween($body, '<!--access start-->', '<!--access end-->'); | ||||
|             if ($accessData) { | ||||
|                 $body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -87,8 +87,12 @@ class OrderController extends Controller | ||||
|         } | ||||
|  | ||||
|         if ($request->input('period') === 'reset_price') { | ||||
|             if ($user->expired_at <= time() || !$user->plan_id) { | ||||
|             if (!$user->plan_id) { | ||||
|                 abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package')); | ||||
|             } else { | ||||
|                 if ($user->plan_id !== $request->input('plan_id')) { | ||||
|                     abort(500, __('This subscription reset package does not apply to your subscription')); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -184,13 +188,17 @@ class OrderController extends Controller | ||||
|         $payment = Payment::find($method); | ||||
|         if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available')); | ||||
|         $paymentService = new PaymentService($payment->payment, $payment->id); | ||||
|         if ($payment->handling_fee_fixed || $payment->handling_fee_percent) { | ||||
|             $order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed); | ||||
|         } | ||||
|         $order->payment_id = $method; | ||||
|         if (!$order->save()) abort(500, __('Request failed, please try again later')); | ||||
|         $result = $paymentService->pay([ | ||||
|             'trade_no' => $tradeNo, | ||||
|             'total_amount' => $order->total_amount, | ||||
|             'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount, | ||||
|             'user_id' => $order->user_id, | ||||
|             'stripe_token' => $request->input('token') | ||||
|         ]); | ||||
|         $order->update(['payment_id' => $method]); | ||||
|         return response([ | ||||
|             'type' => $result['type'], | ||||
|             'data' => $result['data'] | ||||
| @@ -217,7 +225,9 @@ class OrderController extends Controller | ||||
|             'id', | ||||
|             'name', | ||||
|             'payment', | ||||
|             'icon' | ||||
|             'icon', | ||||
|             'handling_fee_fixed', | ||||
|             'handling_fee_percent' | ||||
|         ]) | ||||
|             ->where('enable', 1)->get(); | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| namespace App\Http\Controllers\User; | ||||
|  | ||||
| use App\Http\Controllers\Controller; | ||||
| use App\Models\User; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Plan; | ||||
|  | ||||
| @@ -10,12 +11,15 @@ class PlanController extends Controller | ||||
| { | ||||
|     public function fetch(Request $request) | ||||
|     { | ||||
|         $user = User::find($request->session()->get('id')); | ||||
|         if ($request->input('id')) { | ||||
|             $plan = Plan::where('id', $request->input('id')) | ||||
|                 ->first(); | ||||
|             $plan = Plan::where('id', $request->input('id'))->first(); | ||||
|             if (!$plan) { | ||||
|                 abort(500, __('Subscription plan does not exist')); | ||||
|             } | ||||
|             if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) { | ||||
|                 abort(500, __('Subscription plan does not exist')); | ||||
|             } | ||||
|             return response([ | ||||
|                 'data' => $plan | ||||
|             ]); | ||||
|   | ||||
| @@ -12,15 +12,14 @@ class StatController extends Controller | ||||
|     public function getTrafficLog(Request $request) | ||||
|     { | ||||
|         $builder = StatUser::select([ | ||||
|             DB::raw('sum(u) as u'), | ||||
|             DB::raw('sum(d) as d'), | ||||
|             'u', | ||||
|             'd', | ||||
|             'record_at', | ||||
|             'user_id', | ||||
|             'server_rate' | ||||
|         ]) | ||||
|             ->where('user_id', $request->session()->get('id')) | ||||
|             ->where('record_at', '>=', strtotime(date('Y-m-1'))) | ||||
|             ->groupBy('record_at', 'user_id', 'server_rate') | ||||
|             ->orderBy('record_at', 'DESC'); | ||||
|         return response([ | ||||
|             'data' => $builder->get() | ||||
|   | ||||
| @@ -8,6 +8,7 @@ use App\Http\Requests\User\TicketWithdraw; | ||||
| use App\Jobs\SendTelegramJob; | ||||
| use App\Models\User; | ||||
| use App\Services\TelegramService; | ||||
| use App\Services\TicketService; | ||||
| use App\Utils\Dict; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\Ticket; | ||||
| @@ -40,13 +41,6 @@ class TicketController extends Controller | ||||
|         $ticket = Ticket::where('user_id', $request->session()->get('id')) | ||||
|             ->orderBy('created_at', 'DESC') | ||||
|             ->get(); | ||||
|         for ($i = 0; $i < count($ticket); $i++) { | ||||
|             if ($ticket[$i]['last_reply_user_id'] == $request->session()->get('id')) { | ||||
|                 $ticket[$i]['reply_status'] = 0; | ||||
|             } else { | ||||
|                 $ticket[$i]['reply_status'] = 1; | ||||
|             } | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => $ticket | ||||
|         ]); | ||||
| @@ -55,15 +49,14 @@ class TicketController extends Controller | ||||
|     public function save(TicketSave $request) | ||||
|     { | ||||
|         DB::beginTransaction(); | ||||
|         if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) { | ||||
|         if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->lockForUpdate()->count()) { | ||||
|             abort(500, __('There are other unresolved tickets')); | ||||
|         } | ||||
|         $ticket = Ticket::create(array_merge($request->only([ | ||||
|             'subject', | ||||
|             'level' | ||||
|         ]), [ | ||||
|             'user_id' => $request->session()->get('id'), | ||||
|             'last_reply_user_id' => $request->session()->get('id') | ||||
|             'user_id' => $request->session()->get('id') | ||||
|         ])); | ||||
|         if (!$ticket) { | ||||
|             DB::rollback(); | ||||
| @@ -79,7 +72,7 @@ class TicketController extends Controller | ||||
|             abort(500, __('Failed to open ticket')); | ||||
|         } | ||||
|         DB::commit(); | ||||
|         $this->sendNotify($ticket, $ticketMessage); | ||||
|         $this->sendNotify($ticket, $request->input('message')); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
| @@ -105,19 +98,15 @@ class TicketController extends Controller | ||||
|         if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) { | ||||
|             abort(500, __('Please wait for the technical enginneer to reply')); | ||||
|         } | ||||
|         DB::beginTransaction(); | ||||
|         $ticketMessage = TicketMessage::create([ | ||||
|             'user_id' => $request->session()->get('id'), | ||||
|             'ticket_id' => $ticket->id, | ||||
|             'message' => $request->input('message') | ||||
|         ]); | ||||
|         $ticket->last_reply_user_id = $request->session()->get('id'); | ||||
|         if (!$ticketMessage || !$ticket->save()) { | ||||
|             DB::rollback(); | ||||
|         $ticketService = new TicketService(); | ||||
|         if (!$ticketService->reply( | ||||
|             $ticket, | ||||
|             $request->input('message'), | ||||
|             $request->session()->get('id') | ||||
|         )) { | ||||
|             abort(500, __('Ticket reply failed')); | ||||
|         } | ||||
|         DB::commit(); | ||||
|         $this->sendNotify($ticket, $ticketMessage); | ||||
|         $this->sendNotify($ticket, $request->input('message')); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
| @@ -175,8 +164,7 @@ class TicketController extends Controller | ||||
|         $ticket = Ticket::create([ | ||||
|             'subject' => $subject, | ||||
|             'level' => 2, | ||||
|             'user_id' => $request->session()->get('id'), | ||||
|             'last_reply_user_id' => $request->session()->get('id') | ||||
|             'user_id' => $request->session()->get('id') | ||||
|         ]); | ||||
|         if (!$ticket) { | ||||
|             DB::rollback(); | ||||
| @@ -196,15 +184,15 @@ class TicketController extends Controller | ||||
|             abort(500, __('Failed to open ticket')); | ||||
|         } | ||||
|         DB::commit(); | ||||
|         $this->sendNotify($ticket, $ticketMessage); | ||||
|         $this->sendNotify($ticket, $message); | ||||
|         return response([ | ||||
|             'data' => true | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage) | ||||
|     private function sendNotify(Ticket $ticket, string $message) | ||||
|     { | ||||
|         $telegramService = new TelegramService(); | ||||
|         $telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`", true); | ||||
|         $telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$message}`", true); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; | ||||
| use App\Http\Requests\User\UserTransfer; | ||||
| use App\Http\Requests\User\UserUpdate; | ||||
| use App\Http\Requests\User\UserChangePassword; | ||||
| use App\Services\UserService; | ||||
| use App\Utils\CacheKey; | ||||
| use Illuminate\Http\Request; | ||||
| use App\Models\User; | ||||
| @@ -120,8 +121,9 @@ class UserController extends Controller | ||||
|                 abort(500, __('Subscription plan does not exist')); | ||||
|             } | ||||
|         } | ||||
|         $user['subscribe_url'] = Helper::getSubscribeHost() . "/api/v1/client/subscribe?token={$user['token']}"; | ||||
|         $user['reset_day'] = $this->getResetDay($user); | ||||
|         $user['subscribe_url'] = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); | ||||
|         $userService = new UserService(); | ||||
|         $user['reset_day'] = $userService->getResetDay($user); | ||||
|         return response([ | ||||
|             'data' => $user | ||||
|         ]); | ||||
| @@ -139,7 +141,7 @@ class UserController extends Controller | ||||
|             abort(500, __('Reset failed')); | ||||
|         } | ||||
|         return response([ | ||||
|             'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token | ||||
|             'data' => Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user->token) | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
| @@ -184,36 +186,6 @@ class UserController extends Controller | ||||
|         ]); | ||||
|     } | ||||
|  | ||||
|     private function getResetDay(User $user) | ||||
|     { | ||||
|         if ($user->expired_at <= time() || $user->expired_at === NULL) return null; | ||||
|         // if reset method is not reset | ||||
|         if (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 2) return null; | ||||
|         $day = date('d', $user->expired_at); | ||||
|         $today = date('d'); | ||||
|         $lastDay = date('d', strtotime('last day of +0 months')); | ||||
|  | ||||
|         if ((int)config('v2board.reset_traffic_method') === 0 || | ||||
|             (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 0)) | ||||
|         { | ||||
|             return $lastDay - $today; | ||||
|         } | ||||
|         if ((int)config('v2board.reset_traffic_method') === 1 || | ||||
|             (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 1)) | ||||
|         { | ||||
|             if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) { | ||||
|                 return $lastDay - $today; | ||||
|             } | ||||
|             if ((int)$day >= (int)$today) { | ||||
|                 return $day - $today; | ||||
|             } else { | ||||
|                 return $lastDay - $today + $day; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public function getQuickLoginUrl(Request $request) | ||||
|     { | ||||
|         $user = User::find($request->session()->get('id')); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user