diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index cea00330..c63a19b7 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -13,7 +13,7 @@ use Illuminate\Validation\ValidationException; use Log; use ReflectionException; use Response; -use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Throwable; @@ -25,7 +25,7 @@ class Handler extends ExceptionHandler * @var array */ protected $dontReport = [ - HttpException::class, + // HttpException::class, ValidationException::class, ]; @@ -35,6 +35,7 @@ class Handler extends ExceptionHandler * @var array */ protected $dontFlash = [ + 'current_password', 'password', 'password_confirmation', ]; @@ -105,6 +106,12 @@ class Handler extends ExceptionHandler } return Response::view('auth.error', ['message' => trans('errors.system')], 500); + case $exception instanceof MethodNotAllowedHttpException: + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => trans('errors.http')], 405); + } + + return Response::view('auth.error', ['message' => trans('errors.http')], 405); case $exception instanceof ErrorException: // 捕获系统错误异常 if ($request->ajax() || $request->wantsJson()) { return Response::json([ diff --git a/app/Helpers/ClientApiResponse.php b/app/Helpers/ClientApiResponse.php new file mode 100644 index 00000000..34570e83 --- /dev/null +++ b/app/Helpers/ClientApiResponse.php @@ -0,0 +1,55 @@ +userAgent(), 'bob_vpn')) { + self::$client = 'bob'; + } + } + + public function setClient($client) + { + self::$client = $client; + } + + public function succeed($data = null, $addition = null, $codeResponse = ResponseEnum::HTTP_OK): JsonResponse + { + return $this->jsonResponse(1, $codeResponse, $data, $addition); + } + + private function jsonResponse($status, $codeResponse, $data = null, $addition = null): JsonResponse + { + [$code, $message] = $codeResponse; + $code = $code > 1000 ? (int) ($code / 1000) : $code; + if (self::$client === 'bob') { // bob 客户端 返回格式 + $result = ['ret' => $status, 'msg' => $message, 'data' => $data]; + + if (isset($addition)) { + $result = array_merge($result, $addition); + } + } else { // ProxyPanel client api 规范格式 + if (isset($data, $addition) && is_array($data)) { + $data = array_merge($data, $addition); + } + + $result = ['status' => $status ? 'success' : 'fail', 'code' => $code, 'message' => $message, 'data' => $data ?? $addition]; + } + + return response()->json($result, $code, ['content-type' => 'application/json']); + } + + public function failed($codeResponse = ResponseEnum::HTTP_ERROR, $data = null, $addition = null): JsonResponse + { + return $this->jsonResponse(0, $codeResponse, is_array($data) ? $data[0] : $data, $addition); + } +} diff --git a/app/Helpers/DataChart.php b/app/Helpers/DataChart.php new file mode 100644 index 00000000..f2620c76 --- /dev/null +++ b/app/Helpers/DataChart.php @@ -0,0 +1,54 @@ +whereDate('created_at', + date('Y-m-d'))->selectRaw('(DATE_FORMAT(node_hourly_data_flow.created_at, "%k")) as date, total')->pluck('total', 'date'); + $dailyFlow = NodeDailyDataFlow::whereNodeId($id)->whereMonth('created_at', + date('n'))->selectRaw('(DATE_FORMAT(node_daily_data_flow.created_at, "%e")) as date, total')->pluck('total', 'date'); + } else { + $currentFlow = UserDataFlowLog::whereUserId($id); + $hourlyFlow = UserHourlyDataFlow::userHourly($id)->whereDate('created_at', + date('Y-m-d'))->selectRaw('(DATE_FORMAT(user_hourly_data_flow.created_at, "%k")) as date, total')->pluck('total', 'date'); + $dailyFlow = UserDailyDataFlow::userDaily($id)->whereMonth('created_at', + date('n'))->selectRaw('(DATE_FORMAT(user_daily_data_flow.created_at, "%e")) as date, total')->pluck('total', 'date'); + } + $currentFlow = $currentFlow->where('log_time', '>=', strtotime(date('Y-m-d H:0')))->sum(DB::raw('u + d')); + + // 节点一天内的流量 + $hourlyData = array_fill(0, date('G') + 1, 0); + foreach ($hourlyFlow as $date => $dataFlow) { + $hourlyData[$date] = round($dataFlow / GB, 3); + } + $hourlyData[date('G') + 1] = round($currentFlow / GB, 3); + + // 节点一个月内的流量 + $dailyData = array_fill(0, date('j') - 1, 0); + + foreach ($dailyFlow as $date => $dataFlow) { + $dailyData[$date - 1] = round($dataFlow / GB, 3); + } + + $dailyData[date('j', strtotime(now())) - 1] = round(array_sum($hourlyData) + $currentFlow / GB, 3); + + return [ + 'trafficDaily' => $dailyData, + 'trafficHourly' => $hourlyData, + 'monthDays' => range(1, date('j')), // 本月天数 + 'dayHours' => range(0, date('G') + 1), // 本日小时 + ]; + } +} diff --git a/app/Helpers/ResponseEnum.php b/app/Helpers/ResponseEnum.php new file mode 100644 index 00000000..e363f586 --- /dev/null +++ b/app/Helpers/ResponseEnum.php @@ -0,0 +1,82 @@ +jsonResponse('success', $codeResponse, $data, $addition); + } + + private function jsonResponse($status, $codeResponse, $data, $addition): JsonResponse // 返回数据 + { + [$code, $message] = $codeResponse; + if ($status === 'success') { + $etag = self::abortIfNotModified($data); + } + $code = $code < 1000 ? $code : (int) ($code / 1000); + $data = compact('status', 'code', 'data', 'message'); + if (isset($addition)) { + $data = array_merge($data, $addition); + } + + return response()->json($data, $code, ['ETAG' => $etag ?? '']); + } + + private static function abortIfNotModified($data): string // 检查数据是否有变动 + { + $req = request(); + + if (! $req->isMethod('GET')) { // Only for "GET" method + return ''; + } + + $etag = sha1(json_encode($data)); + if (! empty($req->header('IF-NONE-MATCH')) && hash_equals($etag, $req->header('IF-NONE-MATCH'))) { + abort(304); + } + + return $etag; + } + + public function failed($codeResponse = ResponseEnum::HTTP_ERROR, $data = null, $addition = null): JsonResponse // 失败 + { + return $this->jsonResponse('fail', $codeResponse, $data, $addition); + } +} diff --git a/app/Http/Controllers/Admin/LogsController.php b/app/Http/Controllers/Admin/LogsController.php index 44e7d14b..9fda5ed4 100644 --- a/app/Http/Controllers/Admin/LogsController.php +++ b/app/Http/Controllers/Admin/LogsController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Components\IP; +use App\Helpers\DataChart; use App\Http\Controllers\Controller; use App\Models\Node; use App\Models\NodeOnlineIp; @@ -19,8 +20,9 @@ use Response; class LogsController extends Controller { - // 订单列表 - public function orderList(Request $request) + use DataChart; + + public function orderList(Request $request) // 订单列表 { $query = Order::with(['user:id,username', 'goods:id,name', 'coupon:id,name,sn']); diff --git a/app/Http/Controllers/Admin/NodeController.php b/app/Http/Controllers/Admin/NodeController.php index 07a8d59b..6b19cdda 100644 --- a/app/Http/Controllers/Admin/NodeController.php +++ b/app/Http/Controllers/Admin/NodeController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Components\NetworkDetection; +use App\Helpers\DataChart; use App\Http\Controllers\Controller; use App\Http\Requests\Admin\NodeRequest; use App\Jobs\VNet\reloadNode; @@ -20,8 +21,9 @@ use Response; class NodeController extends Controller { - // 节点列表 - public function index(Request $request) + use DataChart; + + public function index(Request $request) // 节点列表 { $status = $request->input('status'); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index f8ead17c..6354516d 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -15,6 +15,7 @@ use App\Models\User; use App\Models\UserGroup; use App\Models\UserHourlyDataFlow; use App\Models\UserOauth; +use App\Services\ProxyServer; use Arr; use Auth; use Exception; @@ -291,9 +292,11 @@ class UserController extends Controller public function exportProxyConfig(Request $request, User $user): JsonResponse { - $server = Node::findOrFail($request->input('id'))->getConfig($user); // 提取节点信息 + $proxyServer = ProxyServer::getInstance(); + $proxyServer->setUser($user); + $server = $proxyServer->getProxyConfig(Node::findOrFail($request->input('id'))); - return Response::json(['status' => 'success', 'data' => $this->getUserNodeInfo($server, $request->input('type') !== 'text'), 'title' => $server['type']]); + return Response::json(['status' => 'success', 'data' => $proxyServer->getUserProxyConfig($server, $request->input('type') !== 'text'), 'title' => $server['type']]); } public function oauth() diff --git a/app/Http/Controllers/Api/Client/AuthController.php b/app/Http/Controllers/Api/Client/AuthController.php new file mode 100644 index 00000000..fa52f837 --- /dev/null +++ b/app/Http/Controllers/Api/Client/AuthController.php @@ -0,0 +1,105 @@ +userAgent(), 'bob_vpn')) { + $this->setClient('bob'); + } + } + + public function register(Request $request, UserService $userService): JsonResponse + { + $validator = Validator::make($request->all(), [ + 'nickname' => 'required|string|between:2,100', + 'username' => 'required|'.(sysConfig('username_type') ?? 'email').'|max:100|unique:user,username', + 'password' => 'required|string|confirmed|min:6', + ]); + + if ($validator->fails()) { + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); + } + $data = $validator->validated(); + + // 创建新用户 + if ($user = Helpers::addUser($data['username'], $data['password'], (int) sysConfig('default_traffic'), sysConfig('default_days'), null, $data['nickname'])) { + auth()->login($user, true); + + return $this->succeed([ + 'token' => $user->createToken('client')->plainTextToken, + 'expire_in' => time() + config('session.lifetime') * Minute, + 'user' => $userService->getProfile(), + ], null, ResponseEnum::USER_SERVICE_REGISTER_SUCCESS); + } + + return $this->failed(ResponseEnum::USER_SERVICE_REGISTER_ERROR); + } + + public function login(Request $request): JsonResponse + { + if (self::$client === 'bob') { + $rules = [ + 'email' => 'required|'.(sysConfig('username_type') ?? 'email'), + 'passwd' => 'required|string|min:6', + ]; + } else { + $rules = [ + 'username' => 'required|'.(sysConfig('username_type') ?? 'email'), + 'password' => 'required|string|min:6', + ]; + } + $validator = Validator::make($request->all(), $rules); + + if ($validator->fails()) { + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); + } + + if (auth()->attempt(['username' => $request->input('username') ?: $request->input('email'), 'password' => $request->input('password') ?: $request->input('passwd')], + true)) { + $user = auth()->user(); + if ($user && $user->status === -1) { + return $this->failed(ResponseEnum::CLIENT_HTTP_UNAUTHORIZED_BLACKLISTED); + } + if (self::$client === 'bob') { + $request->session()->put('uid', $user->id); + } + + $userService = UserService::getInstance(); + + return $this->succeed([ + 'token' => $user->createToken('client')->plainTextToken, + 'expire_in' => time() + config('session.lifetime') * Minute, + 'user' => $userService->getProfile(), + ], null, ResponseEnum::USER_SERVICE_LOGIN_SUCCESS); + } + + return $this->failed(ResponseEnum::SERVICE_LOGIN_ACCOUNT_ERROR); + } + + public function logout(Request $request): JsonResponse + { + auth()->logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return $this->failed(ResponseEnum::USER_SERVICE_LOGOUT_SUCCESS); + } +} diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php new file mode 100644 index 00000000..ccf2de73 --- /dev/null +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -0,0 +1,304 @@ +userAgent(), 'bob_vpn')) { + $this->setClient('bob'); + } + } + + public function getUserInfo() + { + $user = auth()->user(); + + if (! $user) { + return false; + } + + $userInfo = UserService::getInstance()->getProfile(); + $userInfo['user_name'] = $user->nickname; + $userInfo['email'] = $user->username; + $userInfo['class_expire'] = $user->expiration_date; + $userInfo['money'] = $user->credit; + $userInfo['plan']['name'] = $user->orders()->activePlan()->latest()->first()->goods->name ?? '无'; + $ann = Article::type(2)->latest()->first(); + $user_expire = now()->diffInDays($user->expired_at, false) < 0; + $total = $user->u + $user->d; + $transfer_enable = $user->transfer_enable; + $expired_days = now()->diffInDays($user->expired_at, false); + $userInfo['class_expire_notice'] = ''; + if ($expired_days < 0) { + $userInfo['class_expire_notice'] = '账号会员已过期,请先续费再使用~'; + } elseif ($expired_days > 0 && $expired_days <= config('client.class_expire_notice.days')) { + $userInfo['class_expire_notice'] = sprintf(config('client.class_expire_notice.msg'), $expired_days); + } + + $data['info'] = [ + 'user' => $userInfo, + 'ssrSubToken' => $user->subscribe->code, + 'user_expire' => $user_expire, + 'subUrl' => route('sub', $user->subscribe->code), + 'baseUrl' => sysConfig('subscribe_domain') ?? sysConfig('website_url'), + 'ann' => $ann, + 'avatar' => $user->avatar, + 'usedTraffic' => flowAutoShow($total), + 'enableTraffic' => flowAutoShow($transfer_enable), + 'unUsedTraffic' => flowAutoShow($transfer_enable - $total), + 'reset_time' => now()->diffInDays($user->reset_time, false), + 'android_index_button' => config('client.android_index_button'), + ]; + + return $this->succeed(null, $data); + } + + public function getOrders(Request $request) + { + $user = $request->user(); + $orders = $user->orders()->orderByDesc('id')->limit(8)->get(); + $data = []; + foreach ($orders as $order) { + $data[] = [ + 'id' => $order->id, + 'total_amount' => $order->amount * 100, + 'plan' => ['name' => $order->goods()->value('name') ?? '余额充值'], + 'status' => [-1 => 2, 0 => 0, 1 => 1, 2 => 3, 3 => 4][$order->status], + 'created_at' => strtotime($order->created_at), + ]; + } + + return $this->succeed($data); + } + + public function getUserTransfer() + { + $user = auth()->user(); + + return $this->succeed(null, [ + 'arr' => [ + 'todayUsedTraffic' => flowAutoShow($user->d), + 'lastUsedTraffic' => flowAutoShow($user->u), + 'unUsedTraffic' => flowAutoShow($user->transfer_enable - $user->d - $user->u), + ], + ]); + } + + public function shop() + { + $shops = [ + 'keys' => [], + 'data' => [], + ]; + foreach (GoodsCategory::query()->whereStatus(1)->whereHas('goods')->get() as $category) { + $shops['keys'][] = $category['name']; + $shops['data'][$category['name']] = $category->goods()->get(['name', 'price', 'traffic'])->append('traffic_label')->toArray(); + } + + return $this->succeed($shops); + } + + public function getInvite() + { + $user = auth()->user(); + + $referral_traffic = flowAutoShow(sysConfig('referral_traffic') * MB); + $referral_percent = sysConfig('referral_percent'); + // 邀请码 + $code = $user->invites()->whereStatus(0)->value('code'); + + $data['invite_gift'] = trans('user.invite.promotion', [ + 'traffic' => $referral_traffic, + 'referral_percent' => $referral_percent * 100, + ]); + + $data['invite_code'] = $code ?? UserService::getInstance()->inviteURI(true); + $data['invite_url'] = UserService::getInstance()->inviteURI(); + $data['invite_text'] = $data['invite_url'].'&(复制整段文字到浏览器打开即可访问),找梯子最重要的就是稳定,这个已经上线三年了,一直稳定没有被封过,赶紧下载备用吧!'.($code ? '安装后打开填写我的邀请码【'.$code.'】,你还能多得3天会员.' : ''); + // 累计数据 + $data['back_sum'] = ReferralLog::uid()->sum('commission') / 100; + $data['user_num'] = $user->invitees()->count(); + $data['list'] = $user->invitees()->whereHas('orders', function (Builder $query) { + $query->where('status', '>=', 2)->where('amount', '>', 0); + })->selectRaw('username as user_name, UNIX_TIMESTAMP(created_at) as datetime, id')->orderByDesc('created_at')->limit(10)->get()->toArray(); + foreach ($data['list'] as &$item) { + $item['ref_get'] = ReferralLog::uid()->where('invitee_id', $item['id'])->sum('commission') / 100; + } + + return $this->succeed(null, $data); + } + + public function checkIn(Request $request): JsonResponse + { + $user = $request->user(); + // 系统开启登录加积分功能才可以签到 + if (! sysConfig('is_checkin')) { + return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.disable')]); + } + + // 已签到过,验证是否有效 + if (Cache::has('userCheckIn_'.$user->id)) { + return response()->json(['ret' => 0, 'title' => trans('common.success'), 'msg' => trans('user.home.attendance.done')]); + } + + $traffic = random_int((int) sysConfig('min_rand_traffic'), (int) sysConfig('max_rand_traffic')) * MB; + + if (! $user->incrementData($traffic)) { + return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.failed')]); + } + + // 写入用户流量变动记录 + Helpers::addUserTrafficModifyLog($user->id, null, $user->transfer_enable, $user->transfer_enable + $traffic, trans('user.home.attendance.attribute')); + + // 多久后可以再签到 + $ttl = sysConfig('traffic_limit_time') ? sysConfig('traffic_limit_time') * Minute : Day; + Cache::put('userCheckIn_'.$user->id, '1', $ttl); + + return $this->succeed(null, null, [200, trans('user.home.attendance.success', ['data' => flowAutoShow($traffic)])]); + } + + public function proxyCheck(Request $request) + { + $md5 = $request->get('md5', ''); + + $proxy = ProxyServer::getInstance()->getProxyCode('clash'); + if (strtolower(md5(json_encode($proxy))) === strtolower($md5)) { + return $this->succeed(false); + } + + return $this->succeed(true, ['md5' => strtolower(md5(json_encode($proxy)))]); + } + + public function downloadProxies(Request $request) + { + $flag = strtolower($request->input('flag') ?? ($request->userAgent() ?? '')); + + return ProxyServer::getInstance()->getProxyText($flag === 'v2rayng' ? 'v2rayng' : 'clash', $request->input('type')); + } + + public function getProxyList() + { + $proxyServer = ProxyServer::getInstance(); + + $servers = []; + foreach ($proxyServer->getNodeList(null, false) as $node) { + $server = $proxyServer->getProxyConfig($node); + if ($server['type'] === '`shadowsocks`' || $server['type'] === 'shadowsocksr') { + $server['type'] = 1; + } + + $online_log = $node->onlineLogs->where('log_time', '>=', strtotime('-5 minutes'))->sortBy('log_time')->first(); // 在线人数 + $server['node_ip'] = filter_var($server['host'], FILTER_VALIDATE_IP) ? $server['host'] : gethostbyname($server['host']); + $server['online'] = $online_log->online_user ?? 0; + $this->getOnlineCount($server, $server['online']); + $servers[] = $server; + } + + return $this->succeed($servers); + } + + private function getOnlineCount(&$node, int $online) + { + $node['flag'] = $node['area']; + + if ($online < 15) { + $node['text'] = '⭐ 畅 通'; + $node['color'] = '#28a745'; + } elseif ($online < 30) { + $node['text'] = '💫 拥 挤'; + $node['color'] = '#ffc107'; + } else { + $node['text'] = '🔥 爆 满'; + $node['color'] = '#dc3545'; + } + } + + public function getconfig() + { + $config = $this->clientConfig(); + Arr::forget($config, ['read', 'configured']); + + return $this->succeed(null, ['config' => $config]); + } + + private function clientConfig($key = '') + { + if (! config('client')) { + Artisan::call('config:cache'); + } + + if (config('client.configured') !== true && config('client.read')) { + $this->setClientConfig(); + } + + return $key ? config('client.'.$key) : config('client'); + } + + private function setClientConfig() // + { + $ann = Article::type(2)->latest()->first(); + + if ($ann) { + config(['client.notice.title' => $ann->title, 'client.notice.content' => $ann->content]); + } + config([ + 'client.configured' => true, + 'client.name' => sysConfig('website_name'), + 'client.node_class_name' => Level::all()->pluck('name', 'level')->toArray(), + 'client.baseUrl' => sysConfig('website_url'), + 'client.subscribe_url' => sysConfig('web_api_url') ?? sysConfig('website_url'), + 'client.checkinMin' => sysConfig('min_rand_traffic'), + 'client.checkinMax' => sysConfig('max_rand_traffic'), + 'client.invite_gift' => sysConfig('default_traffic') / 1024, + ]); + } + + public function checkClientVersion(Request $request) + { + $version = $request->input('version'); + $type = $request->input('type'); + $config = $this->clientConfig('vpn_update'); + if (! isset($version, $type)) { + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR); + } + $vpn = $config[$type]; + + if (! $config['enable'] || $vpn['version'] === $version) { + return $this->succeed(null, ['enable' => false]); + } + + return $this->succeed(null, ['enable' => true, 'download_url' => $vpn['download_url'], 'must' => $vpn['must'], 'message' => $vpn['message']], [200, $vpn['message']]); + } + + public function ticketList() + { + $ticket = Ticket::where('user_id', auth()->user()->id)->selectRaw('id, title, UNIX_TIMESTAMP(created_at) as datetime, status')->orderBy('created_at', 'DESC')->get(); + + return $this->succeed($ticket); + } +} diff --git a/app/Http/Controllers/Api/Client/V1Controller.php b/app/Http/Controllers/Api/Client/V1Controller.php deleted file mode 100644 index e6cc8fe6..00000000 --- a/app/Http/Controllers/Api/Client/V1Controller.php +++ /dev/null @@ -1,328 +0,0 @@ -middleware('auth:api')->except('login', 'register', 'shop', 'getConfig'); - auth()->shouldUse('api'); - } - - /** - * @param Request $request - * @return JsonResponse - */ - public static function getStatus(Request $request): JsonResponse - { - $order_id = $request->input('order_id'); - $payment = Order::query()->find($order_id)->payment; - if ($payment) { - if ($payment->status === 1) { - return response()->json(['ret' => 1, 'msg' => '支付成功']); - } - - if ($payment->status === -1) { - return response()->json(['ret' => 0, 'msg' => '订单超时未支付,已自动关闭']); - } - - return response()->json(['ret' => 0, 'msg' => '等待支付']); - } - - return response()->json(['ret' => 0, 'msg' => '未知订单']); - } - - public function login(Request $request) - { - $validator = Validator::make($request->all(), [ - 'username' => 'required|'.(sysConfig('username_type') ?? 'email'), - 'password' => 'required|string|min:6', - ]); - - if ($validator->fails()) { - return response()->json(['ret' => 0, 'msg' => $validator->errors()->all()], 422); - } - - if ($token = auth()->attempt($validator->validated())) { - return $this->createNewToken($token); - } - - return response()->json(['ret' => 0, 'msg' => '登录信息错误'], 401); - } - - protected function createNewToken($token) - { - return response()->json([ - 'ret' => 1, - 'data' => [ - 'access_token' => $token, - 'token_type' => 'bearer', - 'expires_in' => auth()->factory()->getTTL() * 60, - 'user' => auth()->user()->profile(), - ], - ]); - } - - public function register(Request $request) - { - $validator = Validator::make($request->all(), [ - 'nickname' => 'required|string|between:2,100', - 'username' => 'required|'.(sysConfig('username_type') ?? 'email').'|max:100|unique:user,username', - 'password' => 'required|string|confirmed|min:6', - ]); - - if ($validator->fails()) { - return response()->json($validator->errors()->all(), 400); - } - $data = $validator->validated(); - - // 创建新用户 - $user = Helpers::addUser($data['username'], $data['password'], (int) sysConfig('default_traffic'), sysConfig('default_days'), null, $data['nickname']); - - return response()->json(['ret' => 1, 'user' => $user], 201); - } - - public function logout() - { - auth()->logout(); - - return response()->json(['ret' => 1]); - } - - public function refresh() - { - return $this->createNewToken(auth()->refresh()); - } - - public function userProfile() - { - $user = auth()->user(); - $userInfo = $user->profile(); - $userInfo['subUrl'] = $user->subUrl(); - $totalTransfer = $user->transfer_enable; - $usedTransfer = $user->used_traffic; - $unusedTraffic = max($totalTransfer - $usedTransfer, 0); - $userInfo['unusedTraffic'] = flowAutoShow($unusedTraffic); - - return response()->json(['ret' => 1, 'data' => $userInfo]); - } - - public function nodeList() - { - return response()->json(['ret' => 1, 'data' => auth()->user()->nodes()->get()]); - } - - public function shop() - { - $shops = [ - 'keys' => [], - 'data' => [], - ]; - foreach (GoodsCategory::query()->whereStatus(1)->get() as $item) { - $shops['keys'][] = $item['name']; - $shops['data'][$item['name']] = $item->goods()->get()->append('traffic_label')->toArray(); - } - - return response()->json(['ret' => 1, 'data' => $shops]); - } - - public function getConfig() - { - $config = config('bobclient'); - $config['website_name'] = sysConfig('website_name'); - $config['website_url'] = sysConfig('website_url'); - $config['payment'] = [ - 'alipay' => sysConfig('is_AliPay'), - 'wechat' => sysConfig('is_WeChatPay'), - ]; - - return response()->json(['ret' => 1, 'data' => $config]); - } - - public function purchase(Request $request) - { - $goods_id = $request->input('goods_id'); - $coupon_sn = $request->input('coupon_sn'); - self::$method = $request->input('method'); - $credit = $request->input('amount'); - $pay_type = $request->input('pay_type'); - $amount = 0; - - if ($credit) { // 充值余额 - if (! is_numeric($credit) || $credit <= 0) { - return response()->json(['ret' => 0, 'msg' => trans('user.payment.error')]); - } - $amount = $credit; - } elseif ($goods_id && self::$method) { // 购买服务 - $goods = Goods::find($goods_id); - if (! $goods || ! $goods->status) { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:商品已下架']); - } - $amount = $goods->price; - - // 是否有生效的套餐 - $activePlan = Order::userActivePlan()->doesntExist(); - - // 无生效套餐,禁止购买加油包 - if ($goods->type === 1 && $activePlan) { - return response()->json(['ret' => 0, 'msg' => '购买加油包前,请先购买套餐']); - } - - // 单个商品限购 - if ($goods->limit_num) { - $count = Order::uid()->where('status', '>=', 0)->whereGoodsId($goods_id)->count(); - if ($count >= $goods->limit_num) { - return response()->json(['ret' => 0, 'msg' => '此商品限购'.$goods->limit_num.'次,您已购买'.$count.'次']); - } - } - - // 使用优惠券 - if ($coupon_sn) { - $coupon = Coupon::whereStatus(0)->whereIn('type', [1, 2])->whereSn($coupon_sn)->first(); - if (! $coupon) { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:优惠券不存在']); - } - - // 计算实际应支付总价 - $amount = $coupon->type === 2 ? $goods->price * $coupon->value / 100 : $goods->price - $coupon->value; - $amount = $amount > 0 ? round($amount, 2) : 0; // 四舍五入保留2位小数,避免无法正常创建订单 - } - - //非余额付款下,检查在线支付是否开启 - if (self::$method !== 'credit') { - // 判断是否开启在线支付 - if (! sysConfig('is_onlinePay')) { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:系统并未开启在线支付功能']); - } - - // 判断是否存在同个商品的未支付订单 - if (Order::uid()->whereStatus(0)->exists()) { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:尚有未支付的订单,请先去支付']); - } - } elseif (Auth::getUser()->credit < $amount) { // 验证账号余额是否充足 - return response()->json(['ret' => 0, 'msg' => '您的余额不足,请先充值']); - } - - // 价格异常判断 - if ($amount < 0) { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:订单总价异常']); - } - - if ($amount === 0 && self::$method !== 'credit') { - return response()->json(['ret' => 0, 'msg' => '订单创建失败:订单总价为0,无需使用在线支付']); - } - } - - // 生成订单 - try { - $newOrder = Order::create([ - 'sn' => date('ymdHis').random_int(100000, 999999), - 'user_id' => auth()->id(), - 'goods_id' => $credit ? null : $goods_id, - 'coupon_id' => $coupon->id ?? null, - 'origin_amount' => $credit ?: ($goods->price ?? 0), - 'amount' => $amount, - 'pay_type' => $pay_type, - 'pay_way' => self::$method, - ]); - - // 使用优惠券,减少可使用次数 - if (! empty($coupon)) { - if ($coupon->usable_times > 0) { - $coupon->decrement('usable_times'); - } - - Helpers::addCouponLog('订单支付使用', $coupon->id, $goods_id, $newOrder->id); - } - - $request->merge(['id' => $newOrder->id, 'type' => $pay_type, 'amount' => $amount]); - PaymentController::$method = self::$method; - // 生成支付单 - $data = PaymentController::getClient()->purchase($request); - $data = $data->getData(true); - $data['order_id'] = $newOrder->id; - - return response()->json($data); - } catch (Exception $e) { - Log::error('订单生成错误:'.$e->getMessage()); - } - - return response()->json(['ret' => 0, 'msg' => '订单创建失败']); - } - - public function gift(Request $request) - { - $user = $request->user('api'); - $referral_traffic = flowAutoShow(sysConfig('referral_traffic') * MB); - $referral_percent = sysConfig('referral_percent'); - // 邀请码 - $code = $user->invites()->whereStatus(1)->value('code'); - - $data['invite_gift'] = trans('user.invite.promotion', [ - 'traffic' => $referral_traffic, - 'referral_percent' => $referral_percent * 100, - ]); - $affSalt = sysConfig('aff_salt'); - if (isset($affSalt)) { - $aff_link = route('register', ['aff' => (new Hashids($affSalt, 8))->encode($user->id)]); - } else { - $aff_link = route('register', ['aff' => $user->id]); - } - $data['invite_url'] = $aff_link; - $data['invite_text'] = $aff_link.'&(复制整段文字到浏览器打开即可访问),找梯子最重要的就是稳定,这个已经上线三年了,一直稳定没有被封过,赶紧下载备用吧!安装后打开填写我的邀请码【'.$code.'】,你还能多得3天会员.'; - // 累计数据 - $data['back_sum'] = ReferralLog::query()->where('inviter_id', $user->id)->sum('commission') / 100; - $data['user_sum'] = $user->invitees()->count(); - $data['list'] = $user->invitees()->selectRaw('username, UNIX_TIMESTAMP(created_at) as created_at')->limit(10)->get(); - - return response()->json(['ret' => 1, 'data' => $data]); - } - - public function checkIn(Request $request): JsonResponse - { - $user = $request->user(); - // 系统开启登录加积分功能才可以签到 - if (! sysConfig('is_checkin')) { - return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.disable')]); - } - - // 已签到过,验证是否有效 - if (Cache::has('userCheckIn_'.$user->id)) { - return response()->json(['ret' => 0, 'title' => trans('common.success'), 'msg' => trans('user.home.attendance.done')]); - } - - $traffic = random_int((int) sysConfig('min_rand_traffic'), (int) sysConfig('max_rand_traffic')) * MB; - - if (! $user->incrementData($traffic)) { - return response()->json(['ret' => 0, 'title' => trans('common.failed'), 'msg' => trans('user.home.attendance.failed')]); - } - - // 写入用户流量变动记录 - Helpers::addUserTrafficModifyLog($user->id, null, $user->transfer_enable, $user->transfer_enable + $traffic, trans('user.home.attendance.attribute')); - - // 多久后可以再签到 - $ttl = sysConfig('traffic_limit_time') ? sysConfig('traffic_limit_time') * Minute : Day; - Cache::put('userCheckIn_'.$user->id, '1', $ttl); - - return response()->json(['ret' => 1, 'msg' => trans('user.home.attendance.success', ['data' => flowAutoShow($traffic)])]); - } -} diff --git a/app/Http/Controllers/Api/WebApi/CoreController.php b/app/Http/Controllers/Api/WebApi/CoreController.php index 12da12a1..6ae0bd6b 100644 --- a/app/Http/Controllers/Api/WebApi/CoreController.php +++ b/app/Http/Controllers/Api/WebApi/CoreController.php @@ -2,23 +2,26 @@ namespace App\Http\Controllers\Api\WebApi; +use App\Helpers\ResponseEnum; +use App\Helpers\WebApiResponse; use App\Models\Node; use App\Models\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Routing\Controller; -use Response; use Validator; class CoreController extends Controller { + use WebApiResponse; + // 上报节点心跳信息 public function setNodeStatus(Request $request, Node $node): JsonResponse { $validator = Validator::make($request->all(), ['cpu' => 'required', 'mem' => 'required', 'disk' => 'required', 'uptime' => 'required|numeric']); if ($validator->fails()) { - return $this->returnData('上报节点心跳信息失败,请检查字段'); + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); } $data = array_map('intval', $validator->validated()); @@ -28,40 +31,10 @@ class CoreController extends Controller 'load' => implode(' ', [$data['cpu'] / 100, $data['mem'] / 100, $data['disk'] / 100]), 'log_time' => time(), ])) { - return $this->returnData('上报节点心跳信息成功', 200, 'success'); + return $this->succeed(); } - return $this->returnData('生成节点心跳信息失败', 400); - } - - // 返回数据 - public function returnData(string $message, int $code = 422, string $status = 'fail', array $data = [], array $addition = null): JsonResponse - { - $etag = self::abortIfNotModified($data); - $data = compact('status', 'code', 'data', 'message'); - - if (isset($addition)) { - $data = array_merge($data, $addition); - } - - return Response::json($data)->header('ETAG', $etag)->setStatusCode($code); - } - - // 检查数据是否有变动 - private static function abortIfNotModified($data): string - { - $req = request(); - // Only for "GET" method - if (! $req->isMethod('GET')) { - return ''; - } - - $etag = sha1(json_encode($data)); - if (! empty($req->header('IF-NONE-MATCH')) && hash_equals($etag, $req->header('IF-NONE-MATCH'))) { - abort(304); - } - - return $etag; + return $this->failed([400201, '生成节点心跳信息失败']); } // 上报节点在线IP @@ -70,7 +43,7 @@ class CoreController extends Controller $validator = Validator::make($request->all(), ['*.uid' => 'required|numeric|exists:user,id', '*.ip' => 'required|string']); if ($validator->fails()) { - return $this->returnData('上报节点在线用户IP信息失败,请检查字段'); + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); } $onlineCount = 0; @@ -80,14 +53,14 @@ class CoreController extends Controller } if (isset($formattedData) && ! $node->onlineIps()->createMany($formattedData)) { // 生成节点在线IP数据 - return $this->returnData('生成节点在线用户IP信息失败', 400); + return $this->failed([400201, '生成节点在线用户IP信息失败']); } if ($node->onlineLogs()->create(['online_user' => $onlineCount, 'log_time' => time()])) { // 生成节点在线人数数据 - return $this->returnData('上报节点在线情况成功', 200, 'success'); + return $this->succeed(); } - return $this->returnData('生成节点在线情况失败', 400); + return $this->failed([400201, '生成节点在线情况失败']); } // 上报用户流量日志 @@ -96,7 +69,7 @@ class CoreController extends Controller $validator = Validator::make($request->all(), ['*.uid' => 'required|numeric|exists:user,id', '*.upload' => 'required|numeric', '*.download' => 'required|numeric']); if ($validator->fails()) { - return $this->returnData('上报用户流量日志失败,请检查字段'); + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); } foreach ($validator->validated() as $input) { // 处理用户流量数据 @@ -113,10 +86,10 @@ class CoreController extends Controller $user->update(['u' => $user->u + $log->u, 'd' => $user->d + $log->d, 't' => time()]); } - return $this->returnData('上报用户流量日志成功', 200, 'success'); + return $this->succeed(); } - return $this->returnData('生成用户流量日志失败', 400); + return $this->failed([400201, '生成用户流量日志失败']); } // 获取节点的审计规则 @@ -132,11 +105,11 @@ class CoreController extends Controller ]; } - return $this->returnData('获取节点审计规则成功', 200, 'success', ['mode' => $ruleGroup->type ? 'reject' : 'allow', 'rules' => $data ?? []]); + return $this->succeed(['mode' => $ruleGroup->type ? 'reject' : 'allow', 'rules' => $data ?? []]); } // 放行 - return $this->returnData('获取节点审计规则成功', 200, 'success', ['mode' => 'all', 'rules' => $data ?? []]); + return $this->succeed(['mode' => 'all', 'rules' => $data ?? []]); } // 上报用户触发审计规则记录 @@ -145,13 +118,13 @@ class CoreController extends Controller $validator = Validator::make($request->all(), ['uid' => 'required|numeric|exists:user,id', 'rule_id' => 'required|numeric|exists:rule,id', 'reason' => 'required']); if ($validator->fails()) { - return $this->returnData('上报日志失败,请检查字段'); + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR, $validator->errors()->all()); } $data = $validator->validated(); if ($node->ruleLogs()->create(['user_id' => $data['uid'], 'rule_id' => $data['rule_id'], 'reason' => $data['reason']])) { - return $this->returnData('上报日志成功', 200, 'success'); + return $this->succeed(); } - return $this->returnData('上报用户触发审计规则日志失败', 400); + return $this->failed([400201, '上报用户触发审计规则日志失败']); } } diff --git a/app/Http/Controllers/Api/WebApi/SSController.php b/app/Http/Controllers/Api/WebApi/SSController.php index c64188e9..7935d220 100644 --- a/app/Http/Controllers/Api/WebApi/SSController.php +++ b/app/Http/Controllers/Api/WebApi/SSController.php @@ -2,13 +2,16 @@ namespace App\Http\Controllers\Api\WebApi; +use App\Helpers\WebApiResponse; use App\Models\Node; use Illuminate\Http\JsonResponse; +use Illuminate\Routing\Controller; -class SSController extends CoreController +class SSController extends Controller { - // 获取节点信息 - public function getNodeInfo(Node $node): JsonResponse + use WebApiResponse; + + public function getNodeInfo(Node $node): JsonResponse // 获取节点信息 { $data = [ 'id' => $node->id, @@ -22,11 +25,10 @@ class SSController extends CoreController $data['port'] = $node->port; } - return $this->returnData('获取节点信息成功', 200, 'success', $data); + return $this->succeed($data); } - // 获取节点可用的用户列表 - public function getUserList(Node $node): JsonResponse + public function getUserList(Node $node): JsonResponse // 获取节点可用的用户列表 { foreach ($node->users() as $user) { $data[] = [ @@ -38,6 +40,6 @@ class SSController extends CoreController ]; } - return $this->returnData('获取用户列表成功', 200, 'success', $data ?? [], ['updateTime' => time()]); + return $this->succeed($data ?? [], ['updateTime' => time()]); } } diff --git a/app/Http/Controllers/Api/WebApi/SSRController.php b/app/Http/Controllers/Api/WebApi/SSRController.php index 97e5f5a7..e597161b 100644 --- a/app/Http/Controllers/Api/WebApi/SSRController.php +++ b/app/Http/Controllers/Api/WebApi/SSRController.php @@ -2,36 +2,18 @@ namespace App\Http\Controllers\Api\WebApi; +use App\Helpers\WebApiResponse; use App\Models\Node; use Illuminate\Http\JsonResponse; +use Illuminate\Routing\Controller; -class SSRController extends CoreController +class SSRController extends Controller { - // 获取节点信息 - public function getNodeInfo(Node $node): JsonResponse - { - return $this->returnData('获取节点信息成功', 200, 'success', $this->nodeData($node)); - } + use WebApiResponse; - // 生成节点信息 - public function nodeData(Node $node): array + public function getNodeInfo(Node $node): JsonResponse // 获取节点信息 { - return [ - 'id' => $node->id, - 'method' => $node->profile['method'] ?? '', - 'protocol' => $node->profile['protocol'] ?? '', - 'obfs' => $node->profile['obfs'] ?? '', - 'obfs_param' => $node->profile['obfs_param'] ?? '', - 'is_udp' => $node->is_udp, - 'speed_limit' => $node->getRawOriginal('speed_limit'), - 'client_limit' => $node->client_limit, - 'single' => isset($node->profile['passwd']) ? 1 : 0, - 'port' => (string) $node->port, - 'passwd' => $node->profile['passwd'] ?? '', - 'push_port' => $node->push_port, - 'secret' => $node->auth->secret, - 'redirect_url' => sysConfig('redirect_url'), - ]; + return $this->succeed($node->getSSRConfig()); } // 获取节点可用的用户列表 @@ -51,6 +33,6 @@ class SSRController extends CoreController ]; } - return $this->returnData('获取用户列表成功', 200, 'success', $data ?? [], ['updateTime' => time()]); + return $this->succeed($data ?? [], ['updateTime' => time()]); } } diff --git a/app/Http/Controllers/Api/WebApi/TrojanController.php b/app/Http/Controllers/Api/WebApi/TrojanController.php index 1ecd1a19..20b743ef 100644 --- a/app/Http/Controllers/Api/WebApi/TrojanController.php +++ b/app/Http/Controllers/Api/WebApi/TrojanController.php @@ -2,38 +2,40 @@ namespace App\Http\Controllers\Api\WebApi; +use App\Helpers\WebApiResponse; use App\Models\Node; use Illuminate\Http\JsonResponse; +use Illuminate\Routing\Controller; -class TrojanController extends CoreController +class TrojanController extends Controller { - // 获取节点信息 - public function getNodeInfo(Node $node): JsonResponse + use WebApiResponse; + + public function getNodeInfo(Node $node): JsonResponse // 获取节点信息 { - return $this->returnData('获取节点信息成功', 200, 'success', [ - 'id' => $node->id, - 'is_udp' => (bool) $node->is_udp, - 'speed_limit' => $node->getRawOriginal('speed_limit'), + return $this->succeed([ + 'id' => $node->id, + 'is_udp' => (bool) $node->is_udp, + 'speed_limit' => $node->getRawOriginal('speed_limit'), 'client_limit' => $node->client_limit, - 'push_port' => $node->push_port, + 'push_port' => $node->push_port, 'redirect_url' => sysConfig('redirect_url'), - 'trojan_port' => $node->port, - 'secret' => $node->auth->secret, - 'license' => sysConfig('trojan_license'), + 'trojan_port' => $node->port, + 'secret' => $node->auth->secret, + 'license' => sysConfig('trojan_license'), ]); } - // 获取节点可用的用户列表 - public function getUserList(Node $node): JsonResponse + public function getUserList(Node $node): JsonResponse // 获取节点可用的用户列表 { foreach ($node->users() as $user) { $data[] = [ - 'uid' => $user->id, - 'password' => $user->passwd, + 'uid' => $user->id, + 'password' => $user->passwd, 'speed_limit' => $user->getRawOriginal('speed_limit'), ]; } - return $this->returnData('获取用户列表成功', 200, 'success', $data ?? [], ['updateTime' => time()]); + return $this->succeed($data ?? [], ['updateTime' => time()]); } } diff --git a/app/Http/Controllers/Api/WebApi/V2RayController.php b/app/Http/Controllers/Api/WebApi/V2RayController.php index eca52788..93c5ca01 100644 --- a/app/Http/Controllers/Api/WebApi/V2RayController.php +++ b/app/Http/Controllers/Api/WebApi/V2RayController.php @@ -2,28 +2,28 @@ namespace App\Http\Controllers\Api\WebApi; +use App\Helpers\WebApiResponse; use App\Models\Node; use App\Models\NodeCertificate; use Illuminate\Http\JsonResponse; +use Illuminate\Routing\Controller; -class V2RayController extends CoreController +class V2RayController extends Controller { - // 获取节点信息 - public function getNodeInfo(Node $node): JsonResponse + use WebApiResponse; + + public function getNodeInfo(Node $node): JsonResponse // 获取节点信息 { $cert = NodeCertificate::whereDomain($node->profile['v2_host'])->first(); $tlsProvider = ! empty($node->profile['tls_provider']) ? $node->profile['tls_provider'] : sysConfig('v2ray_tls_provider'); - if (! $tlsProvider) { - $tlsProvider = null; - } - return $this->returnData('获取节点信息成功', 200, 'success', [ + return $this->succeed([ 'id' => $node->id, 'is_udp' => (bool) $node->is_udp, 'speed_limit' => $node->getRawOriginal('speed_limit'), 'client_limit' => $node->client_limit, 'push_port' => $node->push_port, - 'redirect_url' => (string) sysConfig('redirect_url'), + 'redirect_url' => (string) sysConfig('redirect_url', ''), 'secret' => $node->auth->secret, 'key' => $cert ? $cert->key : '', 'pem' => $cert ? $cert->pem : '', @@ -40,8 +40,7 @@ class V2RayController extends CoreController ]); } - // 获取节点可用的用户列表 - public function getUserList(Node $node): JsonResponse + public function getUserList(Node $node): JsonResponse // 获取节点可用的用户列表 { foreach ($node->users() as $user) { $data[] = [ @@ -51,19 +50,18 @@ class V2RayController extends CoreController ]; } - return $this->returnData('获取用户列表成功', 200, 'success', $data ?? [], ['updateTime' => time()]); + return $this->succeed($data ?? [], ['updateTime' => time()]); } - // 上报节点伪装域名证书信息 - public function addCertificate(Node $node): JsonResponse + public function addCertificate(Node $node): JsonResponse // 上报节点伪装域名证书信息 { if (request()->has(['key', 'pem'])) { $cert = NodeCertificate::whereDomain($node->v2_host)->firstOrCreate(['domain' => $node->server]); if ($cert && $cert->update(['key' => request('key'), 'pem' => request('pem')])) { - return $this->returnData('上报节点伪装域名证书成功', 200, 'success'); + return $this->succeed(); } } - return $this->returnData('上报节点伪装域名证书失败,请检查字段'); + return $this->failed([400201, '上报节点伪装域名证书失败,请检查字段']); } } diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php deleted file mode 100644 index b931e53c..00000000 --- a/app/Http/Controllers/ClientController.php +++ /dev/null @@ -1,275 +0,0 @@ -quantumultX($user, $servers); - } - if (str_contains($target, 'quantumult')) { - return $this->quantumult($user, $servers); - } - if (str_contains($target, 'clash')) { - return $this->clash($servers); - } - if (str_contains($target, 'surfboard')) { - return $this->surfboard($user, $servers); - } - if (str_contains($target, 'surge')) { - return $this->surge($target, $user, $servers); - } - if (str_contains($target, 'shadowrocket')) { - return $this->shadowrocket($user, $servers); - } - if (str_contains($target, 'v2rayn')) { - return $this->v2rayN($user, $servers); - } - if (str_contains($target, 'v2rayng')) { - return $this->v2rayN($user, $servers); - } - if (str_contains($target, 'v2rayu')) { - return $this->v2rayN($user, $servers); - } -// if (strpos($target, 'shadowsocks') !== false) { -// exit($this->shaodowsocksSIP008($servers)); -// } - return $this->origin($servers); - } - - private function quantumultX(User $user, array $servers = []): string - { - $uri = ''; - if (sysConfig('is_custom_subscribe')) { - header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}"); - } - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $uri .= QuantumultX::buildShadowsocks($server); - } - if ($server['type'] === 'shadowsocksr') { - $uri .= QuantumultX::buildShadowsocksr($server); - } - if ($server['type'] === 'v2ray') { - $uri .= QuantumultX::buildVmess($server); - } - if ($server['type'] === 'trojan') { - $uri .= QuantumultX::buildTrojan($server); - } - } - - return base64_encode($uri); - } - - private function quantumult(User $user, array $servers = []): string - { - if (sysConfig('is_custom_subscribe')) { - header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}"); - } - - return $this->origin($servers); - } - - private function origin(array $servers = [], bool $encode = true): string - { - $uri = ''; - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $uri .= URLSchemes::buildShadowsocks($server); - } - if ($server['type'] === 'shadowsocksr') { - $uri .= URLSchemes::buildShadowsocksr($server); - } - if ($server['type'] === 'v2ray') { - $uri .= URLSchemes::buildVmess($server); - } - if ($server['type'] === 'trojan') { - $uri .= URLSchemes::buildTrojan($server); - } - } - - return $encode ? base64_encode($uri) : $uri; - } - - private function clash($servers) - { - $defaultConfig = base_path().'/resources/rules/default.clash.yaml'; - $customConfig = base_path().'/resources/rules/custom.clash.yaml'; - if (File::exists($customConfig)) { - $config = Yaml::parseFile($customConfig); - } else { - $config = Yaml::parseFile($defaultConfig); - } - - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $proxy[] = Clash::buildShadowsocks($server); - $proxies[] = $server['name']; - } - if ($server['type'] === 'shadowsocksr') { - $proxy[] = Clash::buildShadowsocksr($server); - $proxies[] = $server['name']; - } - if ($server['type'] === 'v2ray') { - $proxy[] = Clash::buildVmess($server); - $proxies[] = $server['name']; - } - if ($server['type'] === 'trojan') { - $proxy[] = Clash::buildTrojan($server); - $proxies[] = $server['name']; - } - } - - $config['proxies'] = array_merge($config['proxies'] ?: [], $proxy ?? []); - foreach ($config['proxy-groups'] as $k => $v) { - if (! is_array($config['proxy-groups'][$k]['proxies'])) { - continue; - } - $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies ?? []); - } - - return str_replace('$app_name', sysConfig('website_name'), Yaml::dump($config)); - } - - private function surfboard(User $user, array $servers = []) - { - $proxies = ''; - $proxyGroup = ''; - - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $proxies .= Surfboard::buildShadowsocks($server); - $proxyGroup .= $server['name'].', '; - } - if ($server['type'] === 'v2ray') { - $proxies .= Surfboard::buildVmess($server); - $proxyGroup .= $server['name'].', '; - } - } - - $defaultConfig = base_path().'/resources/rules/default.surfboard.conf'; - $customConfig = base_path().'/resources/rules/custom.surfboard.conf'; - if (File::exists($customConfig)) { - $config = file_get_contents($customConfig); - } else { - $config = file_get_contents($defaultConfig); - } - - // Subscription link - $subsURL = route('sub', $user->subscribe->code); - - return str_replace(['$subs_link', '$proxies', '$proxy_group'], [$subsURL, $proxies, rtrim($proxyGroup, ', ')], $config); - } - - private function surge(string $target, User $user, array $servers = []) - { - $proxies = ''; - $proxyGroup = ''; - - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $proxies .= Surge::buildShadowsocks($server); - $proxyGroup .= $server['name'].', '; - } - if ($server['type'] === 'v2ray') { - $proxies .= Surge::buildVmess($server); - $proxyGroup .= $server['name'].', '; - } - if ($server['type'] === 'trojan') { - $proxies .= Surge::buildTrojan($server); - $proxyGroup .= $server['name'].', '; - } - } - - if (str_contains($target, 'list')) { - return $proxies; - } - - $defaultConfig = base_path().'/resources/rules/default.surge.conf'; - $customConfig = base_path().'/resources/rules/custom.surge.conf'; - if (File::exists($customConfig)) { - $config = file_get_contents($customConfig); - } else { - $config = file_get_contents($defaultConfig); - } - - // Subscription link - $subsURL = route('sub', $user->subscribe->code); - - return str_replace(['$subs_link', '$proxies', '$proxy_group'], [$subsURL, $proxies, rtrim($proxyGroup, ', ')], $config); - } - - private function shadowrocket(User $user, array $servers = []): string - { - //display remaining traffic and expire date - $uri = ''; - if (sysConfig('is_custom_subscribe')) { - $upload = flowAutoShow($user->u); - $download = flowAutoShow($user->d); - $totalTraffic = flowAutoShow($user->transfer_enable); - $uri = "STATUS=📤:{$upload}📥:{$download}⏳:{$totalTraffic}📅:{$user->expired_at}\r\n"; - } - $uri .= $this->origin($servers, false); - - return base64_encode($uri); - } - - private function shaodowsocksSIP008(array $servers = []): string - { - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocks') { - $configs[] = URLSchemes::buildShadowsocksSIP008($server); - } - } - - return json_encode(['version' => 1, 'remark' => sysConfig('website_name'), 'servers' => $configs ?? []], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); - } - - private function v2rayN(User $user, $servers) - { - $uri = ''; - if (sysConfig('is_custom_subscribe')) { - $text = ''; - if (strtotime($user->expired_at) > time()) { - if ($user->transfer_enable == 0) { - $text .= '剩余流量:0'; - } else { - $text .= '剩余流量:'.flowAutoShow($user->transfer_enable); - } - $text .= ', 过期时间:'.$user->expired_at; - } else { - $text .= '账户已过期,请续费后使用'; - } - $uri .= 'vmess://'.base64url_encode(json_encode([ - 'v' => '2', 'ps' => $text, 'add' => sysConfig('website_url'), 'port' => 0, 'id' => $user->vmess_id, 'aid' => 0, 'net' => 'tcp', - 'type' => 'none', 'host' => sysConfig('website_url'), 'path' => '/', 'tls' => 'tls', - ], JSON_PRETTY_PRINT)).PHP_EOL; - } - - foreach ($servers as $server) { - if ($server['type'] === 'shadowsocksr') { - $uri .= V2rayN::buildShadowsocksr($server); - } - if ($server['type'] === 'v2ray') { - $uri .= V2rayN::buildVmess($server); - } - if ($server['type'] === 'trojan') { - $uri .= V2rayN::buildTrojan($server); - } - } - - return base64_encode($uri); - } -} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 69a06330..ce1176dd 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,14 +2,6 @@ namespace App\Http\Controllers; -use App\Components\Client\Text; -use App\Components\Client\URLSchemes; -use App\Models\NodeDailyDataFlow; -use App\Models\NodeHourlyDataFlow; -use App\Models\UserDailyDataFlow; -use App\Models\UserDataFlowLog; -use App\Models\UserHourlyDataFlow; -use DB; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; @@ -20,94 +12,4 @@ class Controller extends BaseController use AuthorizesRequests; use DispatchesJobs; use ValidatesRequests; - - // 节点信息 - public function getUserNodeInfo(array $server, bool $is_url): ?string - { - $type = $is_url ? new URLSchemes() : new Text(); - switch ($server['type']) { - case'shadowsocks': - $data = $type->buildShadowsocks($server); - break; - case 'shadowsocksr': - $data = $type->buildShadowsocksr($server); - break; - case 'v2ray': - $data = $type->buildVmess($server); - break; - case 'trojan': - $data = $type->buildTrojan($server); - break; - default: - } - - return $data ?? null; - } - - // 流量使用图表 - public function dataFlowChart($id, $is_node = false): array - { - if ($is_node) { - $currentFlow = UserDataFlowLog::whereNodeId($id); - $hourlyFlow = NodeHourlyDataFlow::whereNodeId($id)->whereDate('created_at', date('Y-m-d'))->selectRaw('(DATE_FORMAT(node_hourly_data_flow.created_at, "%k")) as date, total')->pluck('total', 'date'); - $dailyFlow = NodeDailyDataFlow::whereNodeId($id)->whereMonth('created_at', date('n'))->selectRaw('(DATE_FORMAT(node_daily_data_flow.created_at, "%e")) as date, total')->pluck('total', 'date'); - } else { - $currentFlow = UserDataFlowLog::whereUserId($id); - $hourlyFlow = UserHourlyDataFlow::userHourly($id)->whereDate('created_at', date('Y-m-d'))->selectRaw('(DATE_FORMAT(user_hourly_data_flow.created_at, "%k")) as date, total')->pluck('total', 'date'); - $dailyFlow = UserDailyDataFlow::userDaily($id)->whereMonth('created_at', date('n'))->selectRaw('(DATE_FORMAT(user_daily_data_flow.created_at, "%e")) as date, total')->pluck('total', 'date'); - } - $currentFlow = $currentFlow->where('log_time', '>=', strtotime(date('Y-m-d H:0')))->sum(DB::raw('u + d')); - - // 节点一天内的流量 - $hourlyData = array_fill(0, date('G') + 1, 0); - foreach ($hourlyFlow as $date => $dataFlow) { - $hourlyData[$date] = round($dataFlow / GB, 3); - } - $hourlyData[date('G') + 1] = round($currentFlow / GB, 3); - - // 节点一个月内的流量 - $dailyData = array_fill(0, date('j') - 1, 0); - - foreach ($dailyFlow as $date => $dataFlow) { - $dailyData[$date - 1] = round($dataFlow / GB, 3); - } - - $dailyData[date('j', strtotime(now())) - 1] = round(array_sum($hourlyData) + $currentFlow / GB, 3); - - return [ - 'trafficDaily' => $dailyData, - 'trafficHourly' => $hourlyData, - 'monthDays' => range(1, date('j')), // 本月天数 - 'dayHours' => range(0, date('G') + 1), // 本日小时 - ]; - } - - /* - // 将Base64图片转换为本地图片并保存 - public function base64ImageSaver($base64_image_content): ?string - { - // 匹配出图片的格式 - if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)) { - $type = $result[2]; - - $directory = date('Ymd'); - $path = '/assets/images/qrcode/'.$directory.'/'; - // 检查是否有该文件夹,如果没有就创建,并给予最高权限 - if (! file_exists(public_path($path)) - && ! mkdir($concurrentDirectory = public_path($path), 0755, true) - && ! is_dir($concurrentDirectory)) { - throw new RuntimeException(sprintf('Directory "%s" was not created', $concurrentDirectory)); - } - - $fileName = Str::random(18).".{$type}"; - if (file_put_contents(public_path($path.$fileName), base64_decode(str_replace($result[1], '', $base64_image_content)))) { - chmod(public_path($path.$fileName), 0744); - - return $path.$fileName; - } - } - - return ''; - } - */ } diff --git a/app/Http/Controllers/User/AffiliateController.php b/app/Http/Controllers/User/AffiliateController.php index 608aecf8..9dc74817 100644 --- a/app/Http/Controllers/User/AffiliateController.php +++ b/app/Http/Controllers/User/AffiliateController.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Controller; use App\Models\Order; use App\Models\ReferralApply; use App\Models\ReferralLog; +use App\Services\UserService; use Auth; use Hashids\Hashids; use Illuminate\Http\JsonResponse; @@ -20,20 +21,13 @@ class AffiliateController extends Controller return Response::view('auth.error', ['message' => trans('user.purchase_required').''.trans('common.back').''], 402); } - $affSalt = sysConfig('aff_salt'); - if (isset($affSalt)) { - $aff_link = route('register', ['aff' => (new Hashids($affSalt, 8))->encode(Auth::id())]); - } else { - $aff_link = route('register', ['aff' => Auth::id()]); - } - return view('user.referral', [ 'referral_traffic' => flowAutoShow(sysConfig('referral_traffic') * MB), 'referral_percent' => sysConfig('referral_percent'), 'referral_money' => sysConfig('referral_money'), 'totalAmount' => ReferralLog::uid()->sum('commission') / 100, 'canAmount' => ReferralLog::uid()->whereStatus(0)->sum('commission') / 100, - 'aff_link' => $aff_link, + 'aff_link' => UserService::getInstance()->inviteURI(), 'referralLogList' => ReferralLog::uid()->with('invitee:id,username')->latest()->paginate(10, ['*'], 'log_page'), 'referralApplyList' => ReferralApply::uid()->latest()->paginate(10, ['*'], 'apply_page'), 'referralUserList' => Auth::getUser()->invitees()->select(['username', 'created_at'])->latest()->paginate(10, ['*'], 'user_page'), diff --git a/app/Http/Controllers/User/SubscribeController.php b/app/Http/Controllers/User/SubscribeController.php index 09defdbe..c449af6f 100644 --- a/app/Http/Controllers/User/SubscribeController.php +++ b/app/Http/Controllers/User/SubscribeController.php @@ -3,27 +3,30 @@ namespace App\Http\Controllers\User; use App\Components\IP; -use App\Http\Controllers\ClientController; use App\Http\Controllers\Controller; use App\Models\UserSubscribe; use App\Models\UserSubscribeLog; -use Arr; +use App\Services\ProxyServer; use Illuminate\Http\Request; use Redirect; use Response; class SubscribeController extends Controller { - private $subType; + private static $subType; + private $proxyServer; // 通过订阅码获取订阅信息 public function getSubscribeByCode(Request $request, string $code) { - if (empty($code)) { + preg_match('/[0-9A-Za-z]+/', $code, $matches, PREG_UNMATCHED_AS_NULL); + + if (empty($matches) || empty($code)) { return Redirect::route('login'); } - $this->subType = $request->input('type'); - $target = strtolower($request->input('target') ?? ($request->userAgent() ?? '')); + $code = $matches[0]; + $this->proxyServer = ProxyServer::getInstance(); + self::$subType = is_numeric($request->input('type')) ? $request->input('type') : null; // 检查订阅码是否有效 $subscribe = UserSubscribe::whereCode($code)->first(); @@ -61,70 +64,16 @@ class SubscribeController extends Controller return $this->failed(trans('errors.subscribe.question')); } + $this->proxyServer->setUser($user); + $subscribe->increment('times'); // 更新访问次数 + $this->subscribeLog($subscribe->id, IP::getClientIp(), json_encode(['Host' => $request->getHost(), 'User-Agent' => $request->userAgent()])); // 记录每次请求 - // 更新访问次数 - $subscribe->increment('times'); - - // 记录每次请求 - $this->subscribeLog($subscribe->id, IP::getClientIp(), json_encode(['Host' => $request->getHost(), 'User-Agent' => $request->userAgent()])); - - // 获取这个账号可用节点 - $query = $user->nodes()->whereIn('is_display', [2, 3]); - - if ($this->subType === 1) { - $query = $query->whereIn('type', [1, 4]); - } elseif ($this->subType) { - $query = $query->whereType($this->subType); - } - - $nodeList = $query->orderByDesc('sort')->orderBy('id')->get(); - if (empty($nodeList)) { - return $this->failed(trans('errors.subscribe.none')); - } - - $servers = []; - foreach ($nodeList as $node) { - $servers[] = $node->getConfig($user); - } - - // 打乱数组 - if (sysConfig('rand_subscribe')) { - $servers = Arr::shuffle($servers); - } - - if (sysConfig('subscribe_max')) { - $servers = array_slice($servers, 0, (int) sysConfig('subscribe_max')); - } - - return (new ClientController)->config($target, $user, $servers); + return ProxyServer::getInstance()->getProxyText(strtolower($request->input('target') ?? ($request->userAgent() ?? '')), self::$subType); } - // 抛出错误的节点信息,用于兼容防止客户端订阅失败 - private function failed($text) - { - return Response::make(base64url_encode($this->infoGenerator($text))); - } - - private function infoGenerator($text): string - { - switch ($this->subType) { - case 2: - $result = 'vmess://'.base64url_encode(json_encode([ - 'v' => '2', 'ps' => $text, 'add' => '0.0.0.0', 'port' => 0, 'id' => 0, 'aid' => 0, 'net' => 'tcp', - 'type' => 'none', 'host' => '', 'path' => '/', 'tls' => 'tls', - ], JSON_PRETTY_PRINT)); - break; - case 3: - $result = 'trojan://0@0.0.0.0:0?peer=0.0.0.0#'.rawurlencode($text); - break; - case 1: - case 4: - default: - $result = 'ssr://'.base64url_encode('0.0.0.0:0:origin:none:plain:'.base64url_encode('0000').'/?obfsparam=&protoparam=&remarks='.base64url_encode($text).'&group='.base64url_encode(sysConfig('website_name')).'&udpport=0&uot=0'); - break; - } - - return $result.PHP_EOL; + private function failed(string $text) + { // 抛出错误的节点信息,用于兼容防止客户端订阅失败 + return Response::make(base64url_encode($this->proxyServer->failedProxyReturn($text, self::$subType ?? 1))); } private function subscribeLog($subscribeId, $ip, $headers): void diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 2b1f035e..ee347314 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Components\Helpers; +use App\Helpers\DataChart; use App\Models\Article; use App\Models\Coupon; use App\Models\Goods; @@ -15,6 +16,7 @@ use App\Models\User; use App\Notifications\TicketCreated; use App\Notifications\TicketReplied; use App\Services\CouponService; +use App\Services\ProxyServer; use Cache; use DB; use Exception; @@ -32,6 +34,8 @@ use Validator; class UserController extends Controller { + use DataChart; + public function index() { // 用户转换 @@ -108,9 +112,10 @@ class UserController extends Controller { $user = auth()->user(); if ($request->isMethod('POST')) { - $server = Node::findOrFail($request->input('id'))->getConfig($user); // 提取节点信息 + $proxyServer = ProxyServer::getInstance(); + $server = $proxyServer->getProxyConfig(Node::findOrFail($request->input('id'))); - return Response::json(['status' => 'success', 'data' => $this->getUserNodeInfo($server, $request->input('type') !== 'text'), 'title' => $server['type']]); + return Response::json(['status' => 'success', 'data' => $proxyServer->getUserProxyConfig($server, $request->input('type') !== 'text'), 'title' => $server['type']]); } // 获取当前用户可用节点 @@ -477,9 +482,8 @@ class UserController extends Controller ]); } - // 更换订阅地址 public function exchangeSubscribe(): ?JsonResponse - { + { // 更换订阅地址 try { DB::beginTransaction(); @@ -501,9 +505,8 @@ class UserController extends Controller } } - // 转换成管理员的身份 public function switchToAdmin(): JsonResponse - { + { // 转换成管理员的身份 if (! Session::has('admin')) { return Response::json(['status' => 'fail', 'message' => trans('errors.unauthorized')]); } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index b9c21d23..d7cdc289 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,9 +2,11 @@ namespace App\Http; +use App\Http\Middleware\AcceptHeader; use App\Http\Middleware\Affiliate; use App\Http\Middleware\Authenticate; use App\Http\Middleware\CheckForMaintenanceMode; +use App\Http\Middleware\ClientAuthenticate; use App\Http\Middleware\EncryptCookies; use App\Http\Middleware\isForbidden; use App\Http\Middleware\isLogin; @@ -14,6 +16,7 @@ use App\Http\Middleware\Permission; use App\Http\Middleware\RedirectIfAuthenticated; use App\Http\Middleware\SetLocale; use App\Http\Middleware\Telegram; +use App\Http\Middleware\tmpCORS; use App\Http\Middleware\TrimStrings; use App\Http\Middleware\TrustProxies; use App\Http\Middleware\VerifyCsrfToken; @@ -83,6 +86,12 @@ class Kernel extends HttpKernel ], 'api' => [ + AcceptHeader::class, + EncryptCookies::class, + AddQueuedCookiesToResponse::class, + StartSession::class, + tmpCORS::class, + SetLocale::class, 'throttle:60,1', SubstituteBindings::class, ], @@ -96,21 +105,22 @@ class Kernel extends HttpKernel * @var array */ protected $routeMiddleware = [ - 'auth' => Authenticate::class, - 'auth.basic' => AuthenticateWithBasicAuth::class, - 'bindings' => SubstituteBindings::class, - 'cache.headers' => SetCacheHeaders::class, - 'can' => Authorize::class, - 'guest' => RedirectIfAuthenticated::class, + 'auth' => Authenticate::class, + 'auth.basic' => AuthenticateWithBasicAuth::class, + 'auth.client' => ClientAuthenticate::class, + 'bindings' => SubstituteBindings::class, + 'cache.headers' => SetCacheHeaders::class, + 'can' => Authorize::class, + 'guest' => RedirectIfAuthenticated::class, 'password.confirm' => RequirePassword::class, - 'signed' => ValidateSignature::class, - 'telegram'=>Telegram::class, - 'throttle' => ThrottleRequests::class, - 'verified' => EnsureEmailIsVerified::class, - 'webApi' => WebApi::class, - 'isMaintenance' => isMaintenance::class, - 'isSecurity' => isSecurity::class, - 'isForbidden' => isForbidden::class, - 'affiliate' => Affiliate::class, + 'signed' => ValidateSignature::class, + 'telegram' => Telegram::class, + 'throttle' => ThrottleRequests::class, + 'verified' => EnsureEmailIsVerified::class, + 'webApi' => WebApi::class, + 'isMaintenance' => isMaintenance::class, + 'isSecurity' => isSecurity::class, + 'isForbidden' => isForbidden::class, + 'affiliate' => Affiliate::class, ]; } diff --git a/app/Http/Middleware/AcceptHeader.php b/app/Http/Middleware/AcceptHeader.php new file mode 100644 index 00000000..4956f6a4 --- /dev/null +++ b/app/Http/Middleware/AcceptHeader.php @@ -0,0 +1,15 @@ +headers->set('Accept', 'application/json'); + + return $next($request); + } +} diff --git a/app/Http/Middleware/ClientAuthenticate.php b/app/Http/Middleware/ClientAuthenticate.php new file mode 100644 index 00000000..8b7a610e --- /dev/null +++ b/app/Http/Middleware/ClientAuthenticate.php @@ -0,0 +1,30 @@ +session(); + if (isset($session) && ! $session->get('uid')) { + return $this->jsonResponse(-1, ResponseEnum::USER_SERVICE_LOGIN_ERROR); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php index f6c3653c..a73da97a 100644 --- a/app/Http/Middleware/SetLocale.php +++ b/app/Http/Middleware/SetLocale.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; +use Illuminate\Support\Facades\App; use Session; class SetLocale @@ -19,11 +20,12 @@ class SetLocale { if (Session::has('locale')) { app()->setLocale(Session::get('locale')); - } - - if ($request->query('locale')) { + } elseif ($request->query('locale')) { Session::put('locale', $request->query('locale')); app()->setLocale($request->query('locale')); + } elseif ($request->header('content-language')) { + Session::put('locale', $request->header('content-language')); + App::setLocale($request->header('content-language')); } return $next($request); diff --git a/app/Http/Middleware/WebApi.php b/app/Http/Middleware/WebApi.php index 7a599c4e..c3add2ab 100644 --- a/app/Http/Middleware/WebApi.php +++ b/app/Http/Middleware/WebApi.php @@ -2,9 +2,9 @@ namespace App\Http\Middleware; +use App\Helpers\ResponseEnum; +use App\Helpers\WebApiResponse; use Closure; -use Illuminate\Http\JsonResponse; -use Response; class WebApi { @@ -15,31 +15,27 @@ class WebApi * @param Closure $next * @return mixed */ + use WebApiResponse; + public function handle($request, Closure $next) { $node = $request->node; $key = $request->header('key'); $time = $request->header('timestamp'); - if (! isset($key)) {// 未提供 key - return $this->returnData('Your key is null!', 400); + if (! isset($key)) { // 未提供 key + return $this->failed([400200, 'Your key is null!']); } $nodeAuth = $node->auth ?? null; - if (! isset($nodeAuth) || $key !== $nodeAuth->key) {// key不存在/不匹配 - return $this->returnData('Token is invalid!'); + if (! isset($nodeAuth) || $key !== $nodeAuth->key) { // key不存在/不匹配 + return $this->failed(ResponseEnum::CLIENT_PARAMETER_ERROR); } - if (abs($time - time()) >= 300) {// 时差超过5分钟 - return $this->returnData('Please resynchronize the server time!'); + if (abs($time - time()) >= 300) { // 时差超过5分钟 + return $this->failed(ResponseEnum::CLIENT_HTTP_UNSYNCHRONIZE_TIMER); } return $next($request); } - - // 返回数据 - private function returnData(string $message, int $code = 401): JsonResponse - { - return Response::json(['status' => 'fail', 'code' => $code, 'message' => $message]); - } } diff --git a/app/Http/Middleware/tmpCORS.php b/app/Http/Middleware/tmpCORS.php new file mode 100644 index 00000000..6f6b1457 --- /dev/null +++ b/app/Http/Middleware/tmpCORS.php @@ -0,0 +1,28 @@ + null]); + $origin = $request->header('origin'); + if (empty($origin)) { + $referer = $request->header('referer'); + if (! empty($referer) && preg_match("/^((https|http):\/\/)?([^\/]+)/i", $referer, $matches)) { + $origin = $matches[0]; + } + } + $response = $next($request); + $response->header('Access-Control-Allow-Origin', trim($origin, '/')); + $response->header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'); + $response->header('Access-Control-Allow-Headers', 'Content-Type,X-Requested-With'); + $response->header('Access-Control-Allow-Credentials', 'true'); + $response->header('Access-Control-Max-Age', 10080); + + return $response; + } +} diff --git a/app/Jobs/VNet/reloadNode.php b/app/Jobs/VNet/reloadNode.php index fbfd66ba..91bf4c6e 100644 --- a/app/Jobs/VNet/reloadNode.php +++ b/app/Jobs/VNet/reloadNode.php @@ -2,7 +2,6 @@ namespace App\Jobs\VNet; -use App\Http\Controllers\Api\WebApi\SSRController; use Arr; use Exception; use Http; @@ -35,7 +34,7 @@ class reloadNode implements ShouldQueue public function handle(): bool { foreach ($this->nodes as $node) { - $data = (new SSRController())->nodeData($node); + $data = $node->getSSRConfig(); if ($node->is_ddns) { if (! $this->send($node->server.':'.$node->push_port, $node->auth->secret, $data)) { diff --git a/app/Models/Node.php b/app/Models/Node.php index 3dc60803..34577ccb 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -3,7 +3,6 @@ namespace App\Models; use App\Components\IP; -use Arr; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -18,9 +17,7 @@ class Node extends Model { protected $table = 'node'; protected $guarded = []; - protected $casts = [ - 'profile' => 'array', - ]; + protected $casts = ['profile' => 'array']; public function labels() { @@ -62,6 +59,11 @@ class Node extends Model return $this->hasMany(NodeHourlyDataFlow::class); } + public function country(): HasOne + { + return $this->HasOne(Country::class, 'code', 'country_code'); + } + public function ruleGroup(): BelongsTo { return $this->belongsTo(RuleGroup::class); @@ -123,7 +125,7 @@ class Node extends Model public function ips(int $type = 4): array { // 使用DDNS的node先通过gethostbyname获取ip地址 - if ($this->attributes['is_ddns']) { // When ddns is enable, only domain can be used to check the ip + if ($this->attributes['is_ddns'] ?? 0) { // When ddns is enabled, only domain can be used to check the ip $ip = gethostbyname($this->attributes['server']); if (strcmp($ip, $this->attributes['server']) === 0) { Log::warning('获取 【'.$this->attributes['server'].'】 IP失败'.$ip); @@ -136,77 +138,6 @@ class Node extends Model return array_map('trim', explode(',', $ip)); } - public function getConfig(User $user) - { - $config = [ - 'id' => $this->id, - 'name' => $this->name, - 'host' => $this->host, - 'group' => sysConfig('website_name'), - 'udp' => $this->is_udp, - ]; - - if ($this->relay_node_id) { - $parentConfig = $this->relayNode->getConfig($user); - $config = array_merge($config, Arr::except($parentConfig, ['id', 'name', 'host', 'group', 'udp'])); - if ($parentConfig['type'] === 'trojan') { - $config['sni'] = $parentConfig['host']; - } - $config['port'] = $this->port; - } else { - switch ($this->type) { - case 0: - $config = array_merge($config, [ - 'type' => 'shadowsocks', - 'passwd' => $user->passwd, - ], $this->profile); - if ($this->port) { - $config['port'] = $this->port; - } else { - $config['port'] = $user->port; - } - break; - case 2: - $config = array_merge($config, [ - 'type' => 'v2ray', - 'port' => $this->port, - 'uuid' => $user->vmess_id, - ], $this->profile); - break; - case 3: - $config = array_merge($config, [ - 'type' => 'trojan', - 'port' => $this->port, - 'passwd' => $user->passwd, - 'sni' => '', - ], $this->profile); - break; - case 1: - case 4: - $config = array_merge($config, [ - 'type' => 'shadowsocksr', - ], $this->profile); - if ($this->profile['passwd'] && $this->port) { - //单端口使用中转的端口 - $config['port'] = $this->port; - $config['protocol_param'] = $user->port.':'.$user->passwd; - } else { - $config['port'] = $user->port; - $config['passwd'] = $user->passwd; - if ($this->type === 1) { - $config['method'] = $user->method; - $config['protocol'] = $user->protocol; - $config['obfs'] = $user->obfs; - } - } - - break; - } - } - - return $config; - } - public function getSpeedLimitAttribute($value) { return $value / Mbps; @@ -232,4 +163,24 @@ class Node extends Model { return $this->server ?? $this->ip ?? $this->ipv6; } + + public function getSSRConfig(): array + { + return [ + 'id' => $this->id, + 'method' => $this->profile['method'] ?? '', + 'protocol' => $this->profile['protocol'] ?? '', + 'obfs' => $this->profile['obfs'] ?? '', + 'obfs_param' => $this->profile['obfs_param'] ?? '', + 'is_udp' => $this->is_udp, + 'speed_limit' => $this->getRawOriginal('speed_limit'), + 'client_limit' => $this->client_limit, + 'single' => isset($this->profile['passwd']) ? 1 : 0, + 'port' => (string) $this->port, + 'passwd' => $this->profile['passwd'] ?? '', + 'push_port' => $this->push_port, + 'secret' => $this->auth->secret, + 'redirect_url' => sysConfig('redirect_url', ''), + ]; + } } diff --git a/app/Models/User.php b/app/Models/User.php index e9be49a7..4f9b3dcb 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,6 +10,7 @@ use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Kyslik\ColumnSortable\Sortable; +use Laravel\Sanctum\HasApiTokens; use Spatie\Permission\Traits\HasRoles; /** @@ -17,7 +18,7 @@ use Spatie\Permission\Traits\HasRoles; */ class User extends Authenticatable { - use Notifiable, HasRoles, Sortable; + use HasApiTokens, Notifiable, HasRoles, Sortable; public $sortable = ['id', 'credit', 'port', 't', 'expired_at']; protected $table = 'user'; @@ -61,33 +62,6 @@ class User extends Authenticatable return $this->hasMany(UserOauth::class); } - public function profile() - { - return [ - 'id' => $this->id, - 'nickname' => $this->nickname, - 'account' => $this->username, - 'port' => $this->port, - 'passwd' => $this->passwd, - 'uuid' => $this->vmess_id, - 'transfer_enable' => $this->transfer_enable, - 'u' => $this->u, - 'd' => $this->d, - 't' => $this->t, - 'enable' => $this->enable, - 'speed_limit' => $this->speed_limit, - 'credit' => $this->credit, - 'expired_at' => $this->expired_at, - 'ban_time' => $this->ban_time, - 'level' => $this->level_name, - 'group' => $this->userGroup->name ?? null, - 'last_login' => $this->last_login, - 'reset_time' => $this->reset_time, - 'invite_num' => $this->invite_num, - 'status' => $this->status, - ]; - } - public function onlineIpLogs(): HasMany { return $this->hasMany(NodeOnlineIp::class); diff --git a/app/Services/BaseService.php b/app/Services/BaseService.php new file mode 100644 index 00000000..a8fc425d --- /dev/null +++ b/app/Services/BaseService.php @@ -0,0 +1,26 @@ +user(); + } + + public function getUser() + { + return self::$user; + } + + public function setUser(User $user) + { + self::$user = $user; + } + + public function getProxyText($target, $type = null) + { + $servers = $this->getNodeList($type); + if (empty($servers)) { + return $this->failedProxyReturn(trans('errors.subscribe.none'), $type); + } + + if (sysConfig('rand_subscribe')) {// 打乱数组 + $servers = Arr::shuffle($servers); + } + + $max = (int) sysConfig('subscribe_max'); + if ($max) { // 订阅数量限制 + $servers = Arr::random($servers, $max); + } + + return $this->clientConfig($servers, $target); + } + + public function getNodeList($type = null, $isConfig = true) + { + $query = self::$user->nodes()->whereIn('is_display', [2, 3]); // 获取这个账号可用节点 + + if (isset($type)) { + if ($type === 1) { + $query = $query->whereIn('type', [1, 4]); + } elseif ($type) { + $query = $query->whereType($type); + } + } + + $nodes = $query->orderByDesc('sort')->orderBy('id')->get(); + + if ($isConfig) { + $servers = []; + foreach ($nodes as $node) { + $servers[] = $this->getProxyConfig($node); + } + + return $servers; + } + + return $nodes; + } + + public function getProxyConfig(Node $node) // 提取节点信息 + { + $user = self::$user; + $config = [ + 'id' => $node->id, + 'name' => $node->name, + 'area' => $node->country->name, + 'host' => $node->host, + 'group' => sysConfig('website_name'), + 'udp' => $node->is_udp, + ]; + + if ($node->relay_node_id) { + $parentConfig = $this->getProxyConfig($node->relayNode); + $config = array_merge($config, Arr::except($parentConfig, ['id', 'name', 'host', 'group', 'udp'])); + if ($parentConfig['type'] === 'trojan') { + $config['sni'] = $parentConfig['host']; + } + $config['port'] = $node->port; + } else { + switch ($node->type) { + case 0: + $config = array_merge($config, [ + 'type' => 'shadowsocks', + 'passwd' => $user->passwd, + ], $node->profile); + if ($node->port) { + $config['port'] = $node->port; + } else { + $config['port'] = $user->port; + } + break; + case 2: + $config = array_merge($config, [ + 'type' => 'v2ray', + 'port' => $node->port, + 'uuid' => $user->vmess_id, + ], $node->profile); + break; + case 3: + $config = array_merge($config, [ + 'type' => 'trojan', + 'port' => $node->port, + 'passwd' => $user->passwd, + 'sni' => '', + ], $node->profile); + break; + case 1: + case 4: + default: + $config = array_merge($config, ['type' => 'shadowsocksr'], $node->profile); + if ($node->profile['passwd'] && $node->port) { + //单端口使用中转的端口 + $config['port'] = $node->port; + $config['protocol_param'] = $user->port.':'.$user->passwd; + } else { + $config['port'] = $user->port; + $config['passwd'] = $user->passwd; + if ($node->type === 1) { + $config['method'] = $user->method; + $config['protocol'] = $user->protocol; + $config['obfs'] = $user->obfs; + } + } + break; + } + } + + return $config; + } + + public function failedProxyReturn(string $text, $type = 1): string + { + switch ($type) { + case 2: + $url = sysConfig('website_url'); + $result = 'vmess://'.base64url_encode(json_encode([ + 'v' => '2', + 'ps' => $text, + 'add' => $url, + 'port' => 0, + 'id' => 0, + 'aid' => 0, + 'net' => 'tcp', + 'type' => 'none', + 'host' => $url, + 'path' => '/', + 'tls' => 'tls', + ], JSON_PRETTY_PRINT)); + break; + case 3: + $result = 'trojan://0@0.0.0.0:0?peer=0.0.0.0#'.rawurlencode($text); + break; + case 1: + case 4: + default: + $result = 'ssr://'.base64url_encode('0.0.0.0:0:origin:none:plain:'.base64url_encode('0000').'/?obfsparam=&protoparam=&remarks='.base64url_encode($text).'&group='.base64url_encode(sysConfig('website_name')).'&udpport=0&uot=0'); + break; + } + + return $result.PHP_EOL; + } + + public function getProxyCode($target, $type = null) // 客户端用代理信息 + { + $servers = $this->getNodeList($type); + if (empty($servers)) { + return null; + } + + return $this->clientConfig($servers, $target); + } + + public function getUserProxyConfig(array $server, bool $is_url): ?string // 用户显示用代理信息 + { + $type = $is_url ? new URLSchemes() : new Text(); + switch ($server['type']) { + case'shadowsocks': + $data = $type->buildShadowsocks($server); + break; + case 'shadowsocksr': + $data = $type->buildShadowsocksr($server); + break; + case 'v2ray': + $data = $type->buildVmess($server); + break; + case 'trojan': + $data = $type->buildTrojan($server); + break; + default: + $data = null; + } + + return $data; + } +} diff --git a/app/Services/UserService.php b/app/Services/UserService.php new file mode 100644 index 00000000..77e79497 --- /dev/null +++ b/app/Services/UserService.php @@ -0,0 +1,62 @@ +user(); + } + } + + public function getProfile() + { + $user = self::$user; + + return [ + 'id' => $user->id, + 'nickname' => $user->nickname, + 'account' => $user->username, + 'port' => $user->port, + 'passwd' => $user->passwd, + 'uuid' => $user->vmess_id, + 'transfer_enable' => $user->transfer_enable, + 'u' => $user->u, + 'd' => $user->d, + 't' => $user->t, + 'enable' => $user->enable, + 'speed_limit' => $user->speed_limit, + 'credit' => $user->credit, + 'expired_at' => strtotime($user->expired_at), + 'ban_time' => $user->ban_time, + 'level' => $user->level_name, + 'group' => $user->userGroup->name ?? null, + 'last_login' => $user->last_login, + 'reset_time' => $user->reset_date, + 'invite_num' => $user->invite_num, + 'status' => $user->status, + 'invite_url' => $this->inviteURI(), + ]; + } + + public function inviteURI($isCode = false): string + { + $affSalt = sysConfig('aff_salt'); + if (isset($affSalt)) { + $aff = (new Hashids($affSalt, 8))->encode(self::$user->id()); + } else { + $aff = self::$user->id; + } + + return $isCode ? $aff : sysConfig('website_url').route('register', ['aff' => 1], false); + } +} diff --git a/config/client.php b/config/client.php new file mode 100644 index 00000000..a66e4c70 --- /dev/null +++ b/config/client.php @@ -0,0 +1,210 @@ + true, // 由数据库接管基础参数, true 为 接管,false为全部使用本文件中的参数; + // 安卓左侧按钮和跳转地址配置 + 'left_button' => [ + 'button1' => [ + 'text' => '邀请返利', + 'url' => '/user/setting/invite', + 'status' => true, + ], + 'button2' => [ + 'text' => '购买套餐', + 'url' => '/user/shop', + 'status' => true, + ], + 'button3' => [ + 'text' => '官方网站', + 'url' => '/', + 'status' => true, + ], + ], + + // 新版安卓端首页两个按钮的显示和跳转 + 'android_index_button' => [ + [ + 'name' => '商店', + 'url' => '/user/shop', + ], + [ + 'name' => '官网', + 'url' => '/user', + ], + ], + + // .env里面的key值 + 'key' => env('APP_KEY'), + + // 站点名称 + 'name' => 'Bob`s加速器', + + // 面板地址,最后不要带有 / + 'baseUrl' => 'http://www.xxx.com', + + // API地址 + 'subscribe_url' => 'http://api.xxx.com', + + // 签到获得流量 + 'checkinMin' => 1, //用户签到最少流量 单位MB + 'checkinMax' => 50, //用户签到最多流量 + + 'code_payback' => 10, //充值返利百分比 + 'invite_gift' => 2, //邀请新用户获得流量奖励,单位G + + // 软件版本和更新地址 + 'vpn_update' => [ + 'enable' => false, //是否开启更新 + 'android' => [ + 'version' => '2.4.3', // 版本号 + 'download_url' => 'https://ssr.otakuyun.net/clients/otaku_bob.apk', //下载地址 + 'message' => '版本更新:
1.添加点击签到提示框
2.修复剩余流量显示问题', //提示信息 + 'must' => false, //true:强制更新 false:不强制更新 + ], + 'windows' => [ + 'version' => '3.7.0', // 版本号 + 'download_url' => 'https://ssr.otakuyun.net/clients/otaku_bob.exe', //下载地址 + 'message' => '版本更新:
1.修复剩余流量显示问题
2.优化节点测试显示
3.修复弹出网页部分按钮无法使用问题', //提示信息 + 'must' => false, //true:强制更新 false:不强制更新 + ], + 'mac' => [ + 'version' => '3.7.0', // 版本号 + 'download_url' => 'https://ssr.otakuyun.net/clients/otaku_bob.zip', // 下载地址 + 'message' => '版本更新:
1.修复剩余流量显示问题
2.优化节点测试显示
3.修复弹出网页部分按钮无法使用问题', //提示信息 + 'must' => false, // true:强制更新 false:不强制更新 + ], + ], + + // Crisp在线客服 + 'crisp_enable' => false, // 是否开启 + 'crisp_id' => '2c3c28c2-9265-45ea-8e85-0xxxxx', // Crisp 的网站ID + 'crisp_logo_url' => 'http://xxxx/vpn/kefu.png', // Crisp 客服logo + + // 个人中心头像 + 'user_avatar' => 'https://ssr.otakuyun.net/assets/images/avatar.svg', + + 'show_address' => false, // PC端展示用户IP和地址 + 'node_class_name' => [], // 节点的等级对应的名字 格式为 节点等级 => 节点等级名字 + 'hidden_node' => [], // 需要隐藏的节点ID, 数组形式 1,2,3,4 + 'login' => [ // 登录页面配置 + 'telegram_url' => 'https://t.me/otakucloud', // 留空的话则不展示telegram群 + 'qq_url' => 'https://jq.qq.com/?_wv=1027&k=52AI188', // 留空的话则不展示QQ群 + 'background_img' => 'https://ssr.otakuyun.net/assets/images/logo_1.png', // 背景图片地址,图片宽高不超过 860px * 544px 就行 (留空为默认的背景图) + 'text' => '

御宅
飛享

', + 'text_color' => 'rgba(255, 255, 255, 0.8);', // 文字和按钮颜色 默认颜色 rgba(255, 255, 255, 0.8); + 'button_color' => '#667afa', // 文字和按钮颜色 默认颜色:#8077f1(v2版本配置) + ], + + // PC端消息中心图片和跳转链接 + 'message' => [ + 'background_img' => 'https://malus.s3cdn.net/uploads/malus_user-guide.jpg', // 背景图片地址 + 'url' => 'https://www.goole.com', // 跳转链接 + ], + + // 客户端ping检测 1:中转机 2:落地机 + 'ping_test' => 1, + + // 支付 + 'payment' => [ + 'alipay' => 'theadpay', + 'wechat' => 'paybeaver', + 'default' => 'paybeaver', + 'telegram_admin' => 0, // 额外的 Telegram 管理员 ID,接收支付提醒 + 'paybeaver' => [ + 'app_id' => '', + 'app_secret' => '', + 'pay_url' => 'https://api.paybeaver.com', + ], + 'mgate' => [ + 'mgate_api_url' => 'https://api.umipay.net', + 'mgate_app_id' => '', + 'mgate_app_secret' => '', + ], + // stripe支付需要https://dashboard.stripe.com/webhooks去配置好webhook + // 客户端webhook: https://xxxx.com/api/v1/bob/payment/notify + // 然后复制最新的webhook密钥签名到下面的stripe_webhook + 'stripe' => [ + 'stripe_key' => '', + 'stripe_currency' => 'hkd', + 'stripe_webhook' => '', + ], + // 平头哥支付 + 'theadpay' => [ + 'theadpay_url' => 'https://jk.theadpay.com/v1/jk/orders', + 'theadpay_mchid' => '', + 'theadpay_key' => '', + ], + // 易支付 + 'policepay' => [ + 'partner' => '', //商户号 + 'key' => '', //商户key + 'sign_type' => strtoupper('MD5'), + 'input_charset' => strtolower('utf-8'), + 'name' => '手抓饼', //商品名称,目前无意义 + 'transport' => 'https', //访问模式,根据自己的服务器是否支持ssl访问,若支持请选择https;若不支持请选择http + 'appname' => 'PolicePay', //网站英文名 + 'apiurl' => 'https://policepay.cc/', //支付网关 注意结尾的/符号 + 'min_price' => '1', //最小支付金额(请填正数) + ], + // 当面付 + 'facepay' => [ + 'alipay_app_id' => '', //商户号 + 'merchant_private_key' => '', + 'alipay_public_key' => '', + ], + ], + + // 商城配置 + 'shop_plan' => [ + '标准会员' => [1, 2, 3, 4], //对应商店显示的名称 + [商品ID] + '高级会员' => [1, 2, 3, 4], //对应商店显示的名称 + [商品ID] + '至尊会员' => [1, 2, 3, 4], //对应商店显示的名称 + [商品ID] + ], + + // 购买配置 + 'enable_bought_reset' => true, // 购买时是否重置流量 + 'enable_bought_extend' => true, // 购买时是否延长等级期限(同等级配套) + + // 更改订阅方式 + 'clash_online_user' => 1, // 1: 根据在线用户数排序来订阅节点 2: 原版sspanel订阅方式 + + // 检查用户计算机时间 + 'check_time' => [ + 'is_check' => true, // 是否开启检查 + 'differ_time' => 90, // 相差多少秒提示 + 'warning_text' => '请校准系统时间为北京时间,否则会导致无法上网!', // 提示内容 + ], + + // 弹窗公告 + 'notice' => [ + 'is_start' => true, // 是否开启弹窗公告 + 'title' => '最新公告', // 标题 + 'content' => '这是最新 公告 内容', // 公告内容,可以为html格式,也可以纯文本 + ], + + // 用户登录状态保存天数 + 'login_time' => 7, + + // Telegram 机器人 + 'enable_telegram' => false, // 是否开启TG机器人 + 'telegram_token' => '', // Telegram bot,bot 的 token + + // PC端菜单栏显示控制 + 'menu' => [ + 'shop' => true, // 会员 + 'user' => true, // 我的 + 'gift' => true, // 邀请 + ], + + // 安卓端商店显示 + 'android_shop_show' => true, + + // 注册页发送邮件显示 + 'enable_email_verify' => true, + + // 会员即将过期提醒 + 'class_expire_notice' => [ + 'days' => 7, // 多天内过期提醒 + 'msg' => '您好,系统发现您的账号还剩%s天就过期了,请记得及时续费哦~', // 过期提醒文字 (%s不要删,这个是替换天数用的) + ], +]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 00000000..529cfdc9 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,67 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, + ], + +]; diff --git a/config/session.php b/config/session.php index 4e0f66cd..6ba6b4fc 100644 --- a/config/session.php +++ b/config/session.php @@ -168,7 +168,7 @@ return [ | */ - 'secure' => env('SESSION_SECURE_COOKIE'), + 'secure' => env('REDIRECT_HTTPS', true), /* |-------------------------------------------------------------------------- diff --git a/config/version.php b/config/version.php index 7fe7729b..6af4d6f1 100644 --- a/config/version.php +++ b/config/version.php @@ -2,5 +2,5 @@ return [ 'name' => 'ProxyPanel', - 'number' => '2.7.1', + 'number' => '2.7.b', ]; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 00000000..3ce00023 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +} diff --git a/resources/lang/en/errors.php b/resources/lang/en/errors.php index 0f89aa04..becc5d74 100644 --- a/resources/lang/en/errors.php +++ b/resources/lang/en/errors.php @@ -7,6 +7,7 @@ return [ 'china' => 'Chinese IP or Proxy Access Forbidden', 'oversea' => 'Oversea IP or Proxy Access Forbidden', ], + 'http' => 'HTTP Method Not Allowed', 'log' => 'Logs', 'missing_page' => 'Page Not Found', 'refresh' => 'Refresh', diff --git a/resources/lang/en/user.php b/resources/lang/en/user.php index 2a4b3f4c..5e65da2a 100644 --- a/resources/lang/en/user.php +++ b/resources/lang/en/user.php @@ -188,8 +188,9 @@ return [ 'active_prepaid_tips' => 'After active:
Current order will be set to expired!
Expired dates will be recalculated!', ], 'service' => [ - 'node_count' => 'Include :num Nodes', - 'unlimited' => 'Unlimited Speed', + 'node_count' => 'Include :num Nodes', + 'country_count' => 'Covered :num Countries or Areas', + 'unlimited' => 'Unlimited Speed', ], 'node' => [ 'info' => 'Configuration information', diff --git a/resources/lang/zh_CN/errors.php b/resources/lang/zh_CN/errors.php index 250bf961..99464ed0 100644 --- a/resources/lang/zh_CN/errors.php +++ b/resources/lang/zh_CN/errors.php @@ -7,6 +7,7 @@ return [ 'china' => '检测到中国IP或使用代理访问,禁止访问', 'oversea' => '检测到海外IP或代理访问,禁止访问', ], + 'http' => 'HTTP请求类型错误', 'log' => '日志', 'missing_page' => '找不到网页', 'refresh' => '刷 新', diff --git a/resources/lang/zh_CN/user.php b/resources/lang/zh_CN/user.php index 8d20925d..9b0e132b 100644 --- a/resources/lang/zh_CN/user.php +++ b/resources/lang/zh_CN/user.php @@ -172,8 +172,9 @@ return [ 'call4help' => '请开工单通知客服', ], 'service' => [ - 'node_count' => ':num条优质线路', - 'unlimited' => '不限速', + 'node_count' => ':num 条优质线路', + 'country_count' => '覆盖 :num 个国家或地区', + 'unlimited' => '不限速', ], 'payment' => [ 'error' => '充值余额不合规', diff --git a/resources/rules/bob.clash.yaml b/resources/rules/bob.clash.yaml new file mode 100644 index 00000000..e0797d5b --- /dev/null +++ b/resources/rules/bob.clash.yaml @@ -0,0 +1,522 @@ +port: 7890 +socks-port: 7891 +allow-lan: false +mode: rule +log-level: info +external-controller: 127.0.0.1:9090 +experimental: + ignore-resolve-fail: true +dns: + enable: true + ipv6: false + enhanced-mode: redir-host + nameserver: + - 1.2.4.8 + - 223.5.5.5 + fallback: + - tls://1.0.0.1:853 + - tls://dns.google:853 +proxies: + +proxy-groups: + - { name: "Proxy", type: select, proxies: [] } + +rules: + # Apple + - DOMAIN,safebrowsing.urlsec.qq.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。 + - DOMAIN,safebrowsing.googleapis.com,DIRECT # 如果您并不信任此服务提供商或防止其下载消耗过多带宽资源,可以进入 Safari 设置,关闭 Fraudulent Website Warning 功能,并使用 REJECT 策略。 + - DOMAIN,ocsp.apple.com,Proxy + - DOMAIN-SUFFIX,digicert.com,Proxy + - DOMAIN-SUFFIX,entrust.net,Proxy + - DOMAIN,ocsp.verisign.net,Proxy + - DOMAIN-SUFFIX,apps.apple.com,Proxy + - DOMAIN,itunes.apple.com,Proxy + - DOMAIN-SUFFIX,blobstore.apple.com,Proxy + - DOMAIN-SUFFIX,music.apple.com,DIRECT + - DOMAIN-SUFFIX,mzstatic.com,DIRECT + - DOMAIN-SUFFIX,itunes.apple.com,DIRECT + - DOMAIN-SUFFIX,icloud.com,DIRECT + - DOMAIN-SUFFIX,icloud-content.com,DIRECT + - DOMAIN-SUFFIX,me.com,DIRECT + - DOMAIN-SUFFIX,mzstatic.com,DIRECT + - DOMAIN-SUFFIX,akadns.net,DIRECT + - DOMAIN-SUFFIX,aaplimg.com,DIRECT + - DOMAIN-SUFFIX,cdn-apple.com,DIRECT + - DOMAIN-SUFFIX,apple.com,DIRECT + - DOMAIN-SUFFIX,apple-cloudkit.com,DIRECT + # - DOMAIN,e.crashlytics.com,REJECT //注释此选项有助于大多数App开发者分析崩溃信息;如果您拒绝一切崩溃数据统计、搜集,请取消 # 注释。 + + + # 自定义规则 + ## 您可以在此处插入您补充的自定义规则(请注意保持缩进) + + # 国内网站 + - DOMAIN-SUFFIX,cn,DIRECT + - DOMAIN-KEYWORD,-cn,DIRECT + + - DOMAIN-SUFFIX,126.com,DIRECT + - DOMAIN-SUFFIX,126.net,DIRECT + - DOMAIN-SUFFIX,127.net,DIRECT + - DOMAIN-SUFFIX,163.com,DIRECT + - DOMAIN-SUFFIX,360buyimg.com,DIRECT + - DOMAIN-SUFFIX,36kr.com,DIRECT + - DOMAIN-SUFFIX,acfun.tv,DIRECT + - DOMAIN-SUFFIX,air-matters.com,DIRECT + - DOMAIN-SUFFIX,aixifan.com,DIRECT + - DOMAIN-SUFFIX,akamaized.net,DIRECT + - DOMAIN-KEYWORD,alicdn,DIRECT + - DOMAIN-KEYWORD,alipay,DIRECT + - DOMAIN-KEYWORD,taobao,DIRECT + - DOMAIN-SUFFIX,amap.com,DIRECT + - DOMAIN-SUFFIX,autonavi.com,DIRECT + - DOMAIN-KEYWORD,baidu,DIRECT + - DOMAIN-SUFFIX,bdimg.com,DIRECT + - DOMAIN-SUFFIX,bdstatic.com,DIRECT + - DOMAIN-SUFFIX,bilibili.com,DIRECT + - DOMAIN-SUFFIX,bilivideo.com,DIRECT + - DOMAIN-SUFFIX,caiyunapp.com,DIRECT + - DOMAIN-SUFFIX,clouddn.com,DIRECT + - DOMAIN-SUFFIX,cnbeta.com,DIRECT + - DOMAIN-SUFFIX,cnbetacdn.com,DIRECT + - DOMAIN-SUFFIX,cootekservice.com,DIRECT + - DOMAIN-SUFFIX,csdn.net,DIRECT + - DOMAIN-SUFFIX,ctrip.com,DIRECT + - DOMAIN-SUFFIX,dgtle.com,DIRECT + - DOMAIN-SUFFIX,dianping.com,DIRECT + - DOMAIN-SUFFIX,douban.com,DIRECT + - DOMAIN-SUFFIX,doubanio.com,DIRECT + - DOMAIN-SUFFIX,duokan.com,DIRECT + - DOMAIN-SUFFIX,easou.com,DIRECT + - DOMAIN-SUFFIX,ele.me,DIRECT + - DOMAIN-SUFFIX,feng.com,DIRECT + - DOMAIN-SUFFIX,fir.im,DIRECT + - DOMAIN-SUFFIX,frdic.com,DIRECT + - DOMAIN-SUFFIX,g-cores.com,DIRECT + - DOMAIN-SUFFIX,godic.net,DIRECT + - DOMAIN-SUFFIX,gtimg.com,DIRECT + - DOMAIN,cdn.hockeyapp.net,DIRECT + - DOMAIN-SUFFIX,hdslb.com,DIRECT + - DOMAIN-SUFFIX,hongxiu.com,DIRECT + - DOMAIN-SUFFIX,hxcdn.net,DIRECT + - DOMAIN-SUFFIX,iciba.com,DIRECT + - DOMAIN-SUFFIX,ifeng.com,DIRECT + - DOMAIN-SUFFIX,ifengimg.com,DIRECT + - DOMAIN-SUFFIX,ipip.net,DIRECT + - DOMAIN-SUFFIX,iqiyi.com,DIRECT + - DOMAIN-SUFFIX,jd.com,DIRECT + - DOMAIN-SUFFIX,jianshu.com,DIRECT + - DOMAIN-SUFFIX,knewone.com,DIRECT + - DOMAIN-SUFFIX,le.com,DIRECT + - DOMAIN-SUFFIX,lecloud.com,DIRECT + - DOMAIN-SUFFIX,lemicp.com,DIRECT + - DOMAIN-SUFFIX,licdn.com,DIRECT + - DOMAIN-SUFFIX,linkedin.com,DIRECT + - DOMAIN-SUFFIX,luoo.net,DIRECT + - DOMAIN-SUFFIX,meituan.com,DIRECT + - DOMAIN-SUFFIX,meituan.net,DIRECT + - DOMAIN-SUFFIX,mi.com,DIRECT + - DOMAIN-SUFFIX,miaopai.com,DIRECT + - DOMAIN-SUFFIX,microsoft.com,DIRECT + - DOMAIN-SUFFIX,microsoftonline.com,DIRECT + - DOMAIN-SUFFIX,miui.com,DIRECT + - DOMAIN-SUFFIX,miwifi.com,DIRECT + - DOMAIN-SUFFIX,mob.com,DIRECT + - DOMAIN-SUFFIX,netease.com,DIRECT + - DOMAIN-SUFFIX,office.com,DIRECT + - DOMAIN-SUFFIX,office365.com,DIRECT + - DOMAIN-KEYWORD,officecdn,DIRECT + - DOMAIN-SUFFIX,oschina.net,DIRECT + - DOMAIN-SUFFIX,ppsimg.com,DIRECT + - DOMAIN-SUFFIX,pstatp.com,DIRECT + - DOMAIN-SUFFIX,qcloud.com,DIRECT + - DOMAIN-SUFFIX,qdaily.com,DIRECT + - DOMAIN-SUFFIX,qdmm.com,DIRECT + - DOMAIN-SUFFIX,qhimg.com,DIRECT + - DOMAIN-SUFFIX,qhres.com,DIRECT + - DOMAIN-SUFFIX,qidian.com,DIRECT + - DOMAIN-SUFFIX,qihucdn.com,DIRECT + - DOMAIN-SUFFIX,qiniu.com,DIRECT + - DOMAIN-SUFFIX,qiniucdn.com,DIRECT + - DOMAIN-SUFFIX,qiyipic.com,DIRECT + - DOMAIN-SUFFIX,qq.com,DIRECT + - DOMAIN-SUFFIX,qqurl.com,DIRECT + - DOMAIN-SUFFIX,rarbg.to,DIRECT + - DOMAIN-SUFFIX,ruguoapp.com,DIRECT + - DOMAIN-SUFFIX,segmentfault.com,DIRECT + - DOMAIN-SUFFIX,sinaapp.com,DIRECT + - DOMAIN-SUFFIX,smzdm.com,DIRECT + - DOMAIN-SUFFIX,snapdrop.net,DIRECT + - DOMAIN-SUFFIX,sogou.com,DIRECT + - DOMAIN-SUFFIX,sogoucdn.com,DIRECT + - DOMAIN-SUFFIX,sohu.com,DIRECT + - DOMAIN-SUFFIX,soku.com,DIRECT + - DOMAIN-SUFFIX,speedtest.net,DIRECT + - DOMAIN-SUFFIX,sspai.com,DIRECT + - DOMAIN-SUFFIX,suning.com,DIRECT + - DOMAIN-SUFFIX,taobao.com,DIRECT + - DOMAIN-SUFFIX,tencent.com,DIRECT + - DOMAIN-SUFFIX,tenpay.com,DIRECT + - DOMAIN-SUFFIX,tianyancha.com,DIRECT + - DOMAIN-SUFFIX,tmall.com,DIRECT + - DOMAIN-SUFFIX,tudou.com,DIRECT + - DOMAIN-SUFFIX,umetrip.com,DIRECT + - DOMAIN-SUFFIX,upaiyun.com,DIRECT + - DOMAIN-SUFFIX,upyun.com,DIRECT + - DOMAIN-SUFFIX,veryzhun.com,DIRECT + - DOMAIN-SUFFIX,weather.com,DIRECT + - DOMAIN-SUFFIX,weibo.com,DIRECT + - DOMAIN-SUFFIX,xiami.com,DIRECT + - DOMAIN-SUFFIX,xiami.net,DIRECT + - DOMAIN-SUFFIX,xiaomicp.com,DIRECT + - DOMAIN-SUFFIX,ximalaya.com,DIRECT + - DOMAIN-SUFFIX,xmcdn.com,DIRECT + - DOMAIN-SUFFIX,xunlei.com,DIRECT + - DOMAIN-SUFFIX,yhd.com,DIRECT + - DOMAIN-SUFFIX,yihaodianimg.com,DIRECT + - DOMAIN-SUFFIX,yinxiang.com,DIRECT + - DOMAIN-SUFFIX,ykimg.com,DIRECT + - DOMAIN-SUFFIX,youdao.com,DIRECT + - DOMAIN-SUFFIX,youku.com,DIRECT + - DOMAIN-SUFFIX,zealer.com,DIRECT + - DOMAIN-SUFFIX,zhihu.com,DIRECT + - DOMAIN-SUFFIX,zhimg.com,DIRECT + - DOMAIN-SUFFIX,zimuzu.tv,DIRECT + - DOMAIN-SUFFIX,zoho.com,DIRECT + + # 抗 DNS 污染 + - DOMAIN-KEYWORD,amazon,Proxy + - DOMAIN-KEYWORD,google,Proxy + - DOMAIN-KEYWORD,gmail,Proxy + - DOMAIN-KEYWORD,youtube,Proxy + - DOMAIN-KEYWORD,facebook,Proxy + - DOMAIN-SUFFIX,fb.me,Proxy + - DOMAIN-SUFFIX,fbcdn.net,Proxy + - DOMAIN-KEYWORD,twitter,Proxy + - DOMAIN-KEYWORD,instagram,Proxy + - DOMAIN-KEYWORD,dropbox,Proxy + - DOMAIN-SUFFIX,twimg.com,Proxy + - DOMAIN-KEYWORD,blogspot,Proxy + - DOMAIN-SUFFIX,youtu.be,Proxy + - DOMAIN-KEYWORD,whatsapp,Proxy + + # 常见广告域名屏蔽 + - DOMAIN-KEYWORD,admarvel,REJECT + - DOMAIN-KEYWORD,admaster,REJECT + - DOMAIN-KEYWORD,adsage,REJECT + - DOMAIN-KEYWORD,adsmogo,REJECT + - DOMAIN-KEYWORD,adsrvmedia,REJECT + - DOMAIN-KEYWORD,adwords,REJECT + - DOMAIN-KEYWORD,adservice,REJECT + - DOMAIN-KEYWORD,domob,REJECT + - DOMAIN-KEYWORD,duomeng,REJECT + - DOMAIN-KEYWORD,dwtrack,REJECT + - DOMAIN-KEYWORD,guanggao,REJECT + - DOMAIN-KEYWORD,lianmeng,REJECT + - DOMAIN-SUFFIX,mmstat.com,REJECT + - DOMAIN-KEYWORD,omgmta,REJECT + - DOMAIN-KEYWORD,openx,REJECT + - DOMAIN-KEYWORD,partnerad,REJECT + - DOMAIN-KEYWORD,pingfore,REJECT + - DOMAIN-KEYWORD,supersonicads,REJECT + - DOMAIN-KEYWORD,tracking,REJECT + - DOMAIN-KEYWORD,uedas,REJECT + - DOMAIN-KEYWORD,umeng,REJECT + - DOMAIN-KEYWORD,usage,REJECT + - DOMAIN-KEYWORD,wlmonitor,REJECT + - DOMAIN-KEYWORD,zjtoolbar,REJECT + + # 国外网站 + - DOMAIN-SUFFIX,9to5mac.com,Proxy + - DOMAIN-SUFFIX,abpchina.org,Proxy + - DOMAIN-SUFFIX,adblockplus.org,Proxy + - DOMAIN-SUFFIX,adobe.com,Proxy + - DOMAIN-SUFFIX,alfredapp.com,Proxy + - DOMAIN-SUFFIX,amplitude.com,Proxy + - DOMAIN-SUFFIX,ampproject.org,Proxy + - DOMAIN-SUFFIX,android.com,Proxy + - DOMAIN-SUFFIX,angularjs.org,Proxy + - DOMAIN-SUFFIX,aolcdn.com,Proxy + - DOMAIN-SUFFIX,apkpure.com,Proxy + - DOMAIN-SUFFIX,appledaily.com,Proxy + - DOMAIN-SUFFIX,appshopper.com,Proxy + - DOMAIN-SUFFIX,appspot.com,Proxy + - DOMAIN-SUFFIX,arcgis.com,Proxy + - DOMAIN-SUFFIX,archive.org,Proxy + - DOMAIN-SUFFIX,armorgames.com,Proxy + - DOMAIN-SUFFIX,aspnetcdn.com,Proxy + - DOMAIN-SUFFIX,att.com,Proxy + - DOMAIN-SUFFIX,awsstatic.com,Proxy + - DOMAIN-SUFFIX,azureedge.net,Proxy + - DOMAIN-SUFFIX,azurewebsites.net,Proxy + - DOMAIN-SUFFIX,bing.com,Proxy + - DOMAIN-SUFFIX,bintray.com,Proxy + - DOMAIN-SUFFIX,bit.com,Proxy + - DOMAIN-SUFFIX,bit.ly,Proxy + - DOMAIN-SUFFIX,bitbucket.org,Proxy + - DOMAIN-SUFFIX,bjango.com,Proxy + - DOMAIN-SUFFIX,bkrtx.com,Proxy + - DOMAIN-SUFFIX,blog.com,Proxy + - DOMAIN-SUFFIX,blogcdn.com,Proxy + - DOMAIN-SUFFIX,blogger.com,Proxy + - DOMAIN-SUFFIX,blogsmithmedia.com,Proxy + - DOMAIN-SUFFIX,blogspot.com,Proxy + - DOMAIN-SUFFIX,blogspot.hk,Proxy + - DOMAIN-SUFFIX,bloomberg.com,Proxy + - DOMAIN-SUFFIX,box.com,Proxy + - DOMAIN-SUFFIX,box.net,Proxy + - DOMAIN-SUFFIX,cachefly.net,Proxy + - DOMAIN-SUFFIX,chromium.org,Proxy + - DOMAIN-SUFFIX,cl.ly,Proxy + - DOMAIN-SUFFIX,cloudflare.com,Proxy + - DOMAIN-SUFFIX,cloudfront.net,Proxy + - DOMAIN-SUFFIX,cloudmagic.com,Proxy + - DOMAIN-SUFFIX,cmail19.com,Proxy + - DOMAIN-SUFFIX,cnet.com,Proxy + - DOMAIN-SUFFIX,cocoapods.org,Proxy + - DOMAIN-SUFFIX,comodoca.com,Proxy + - DOMAIN-SUFFIX,crashlytics.com,Proxy + - DOMAIN-SUFFIX,culturedcode.com,Proxy + - DOMAIN-SUFFIX,d.pr,Proxy + - DOMAIN-SUFFIX,danilo.to,Proxy + - DOMAIN-SUFFIX,dayone.me,Proxy + - DOMAIN-SUFFIX,db.tt,Proxy + - DOMAIN-SUFFIX,deskconnect.com,Proxy + - DOMAIN-SUFFIX,disq.us,Proxy + - DOMAIN-SUFFIX,disqus.com,Proxy + - DOMAIN-SUFFIX,disquscdn.com,Proxy + - DOMAIN-SUFFIX,dnsimple.com,Proxy + - DOMAIN-SUFFIX,docker.com,Proxy + - DOMAIN-SUFFIX,dribbble.com,Proxy + - DOMAIN-SUFFIX,droplr.com,Proxy + - DOMAIN-SUFFIX,duckduckgo.com,Proxy + - DOMAIN-SUFFIX,dueapp.com,Proxy + - DOMAIN-SUFFIX,dytt8.net,Proxy + - DOMAIN-SUFFIX,edgecastcdn.net,Proxy + - DOMAIN-SUFFIX,edgekey.net,Proxy + - DOMAIN-SUFFIX,edgesuite.net,Proxy + - DOMAIN-SUFFIX,engadget.com,Proxy + - DOMAIN-SUFFIX,entrust.net,Proxy + - DOMAIN-SUFFIX,eurekavpt.com,Proxy + - DOMAIN-SUFFIX,evernote.com,Proxy + - DOMAIN-SUFFIX,fabric.io,Proxy + - DOMAIN-SUFFIX,fast.com,Proxy + - DOMAIN-SUFFIX,fastly.net,Proxy + - DOMAIN-SUFFIX,fc2.com,Proxy + - DOMAIN-SUFFIX,feedburner.com,Proxy + - DOMAIN-SUFFIX,feedly.com,Proxy + - DOMAIN-SUFFIX,feedsportal.com,Proxy + - DOMAIN-SUFFIX,fiftythree.com,Proxy + - DOMAIN-SUFFIX,firebaseio.com,Proxy + - DOMAIN-SUFFIX,flexibits.com,Proxy + - DOMAIN-SUFFIX,flickr.com,Proxy + - DOMAIN-SUFFIX,flipboard.com,Proxy + - DOMAIN-SUFFIX,g.co,Proxy + - DOMAIN-SUFFIX,gabia.net,Proxy + - DOMAIN-SUFFIX,geni.us,Proxy + - DOMAIN-SUFFIX,gfx.ms,Proxy + - DOMAIN-SUFFIX,ggpht.com,Proxy + - DOMAIN-SUFFIX,ghostnoteapp.com,Proxy + - DOMAIN-SUFFIX,git.io,Proxy + - DOMAIN-KEYWORD,github,Proxy + - DOMAIN-SUFFIX,globalsign.com,Proxy + - DOMAIN-SUFFIX,gmodules.com,Proxy + - DOMAIN-SUFFIX,godaddy.com,Proxy + - DOMAIN-SUFFIX,golang.org,Proxy + - DOMAIN-SUFFIX,gongm.in,Proxy + - DOMAIN-SUFFIX,goo.gl,Proxy + - DOMAIN-SUFFIX,goodreaders.com,Proxy + - DOMAIN-SUFFIX,goodreads.com,Proxy + - DOMAIN-SUFFIX,gravatar.com,Proxy + - DOMAIN-SUFFIX,gstatic.com,Proxy + - DOMAIN-SUFFIX,gvt0.com,Proxy + - DOMAIN-SUFFIX,hockeyapp.net,Proxy + - DOMAIN-SUFFIX,hotmail.com,Proxy + - DOMAIN-SUFFIX,icons8.com,Proxy + - DOMAIN-SUFFIX,ifixit.com,Proxy + - DOMAIN-SUFFIX,ift.tt,Proxy + - DOMAIN-SUFFIX,ifttt.com,Proxy + - DOMAIN-SUFFIX,iherb.com,Proxy + - DOMAIN-SUFFIX,imageshack.us,Proxy + - DOMAIN-SUFFIX,img.ly,Proxy + - DOMAIN-SUFFIX,imgur.com,Proxy + - DOMAIN-SUFFIX,imore.com,Proxy + - DOMAIN-SUFFIX,instapaper.com,Proxy + - DOMAIN-SUFFIX,ipn.li,Proxy + - DOMAIN-SUFFIX,is.gd,Proxy + - DOMAIN-SUFFIX,issuu.com,Proxy + - DOMAIN-SUFFIX,itgonglun.com,Proxy + - DOMAIN-SUFFIX,itun.es,Proxy + - DOMAIN-SUFFIX,ixquick.com,Proxy + - DOMAIN-SUFFIX,j.mp,Proxy + - DOMAIN-SUFFIX,js.revsci.net,Proxy + - DOMAIN-SUFFIX,jshint.com,Proxy + - DOMAIN-SUFFIX,jtvnw.net,Proxy + - DOMAIN-SUFFIX,justgetflux.com,Proxy + - DOMAIN-SUFFIX,kat.cr,Proxy + - DOMAIN-SUFFIX,klip.me,Proxy + - DOMAIN-SUFFIX,libsyn.com,Proxy + - DOMAIN-SUFFIX,linode.com,Proxy + - DOMAIN-SUFFIX,lithium.com,Proxy + - DOMAIN-SUFFIX,littlehj.com,Proxy + - DOMAIN-SUFFIX,live.com,Proxy + - DOMAIN-SUFFIX,live.net,Proxy + - DOMAIN-SUFFIX,livefilestore.com,Proxy + - DOMAIN-SUFFIX,llnwd.net,Proxy + - DOMAIN-SUFFIX,macid.co,Proxy + - DOMAIN-SUFFIX,macromedia.com,Proxy + - DOMAIN-SUFFIX,macrumors.com,Proxy + - DOMAIN-SUFFIX,mashable.com,Proxy + - DOMAIN-SUFFIX,mathjax.org,Proxy + - DOMAIN-SUFFIX,medium.com,Proxy + - DOMAIN-SUFFIX,mega.co.nz,Proxy + - DOMAIN-SUFFIX,mega.nz,Proxy + - DOMAIN-SUFFIX,megaupload.com,Proxy + - DOMAIN-SUFFIX,microsofttranslator.com,Proxy + - DOMAIN-SUFFIX,mindnode.com,Proxy + - DOMAIN-SUFFIX,mobile01.com,Proxy + - DOMAIN-SUFFIX,modmyi.com,Proxy + - DOMAIN-SUFFIX,msedge.net,Proxy + - DOMAIN-SUFFIX,myfontastic.com,Proxy + - DOMAIN-SUFFIX,name.com,Proxy + - DOMAIN-SUFFIX,nextmedia.com,Proxy + - DOMAIN-SUFFIX,nsstatic.net,Proxy + - DOMAIN-SUFFIX,nssurge.com,Proxy + - DOMAIN-SUFFIX,nyt.com,Proxy + - DOMAIN-SUFFIX,nytimes.com,Proxy + - DOMAIN-SUFFIX,omnigroup.com,Proxy + - DOMAIN-SUFFIX,onedrive.com,Proxy + - DOMAIN-SUFFIX,onenote.com,Proxy + - DOMAIN-SUFFIX,ooyala.com,Proxy + - DOMAIN-SUFFIX,openvpn.net,Proxy + - DOMAIN-SUFFIX,openwrt.org,Proxy + - DOMAIN-SUFFIX,orkut.com,Proxy + - DOMAIN-SUFFIX,osxdaily.com,Proxy + - DOMAIN-SUFFIX,outlook.com,Proxy + - DOMAIN-SUFFIX,ow.ly,Proxy + - DOMAIN-SUFFIX,paddleapi.com,Proxy + - DOMAIN-SUFFIX,parallels.com,Proxy + - DOMAIN-SUFFIX,parse.com,Proxy + - DOMAIN-SUFFIX,pdfexpert.com,Proxy + - DOMAIN-SUFFIX,periscope.tv,Proxy + - DOMAIN-SUFFIX,pinboard.in,Proxy + - DOMAIN-SUFFIX,pinterest.com,Proxy + - DOMAIN-SUFFIX,pixelmator.com,Proxy + - DOMAIN-SUFFIX,pixiv.net,Proxy + - DOMAIN-SUFFIX,playpcesor.com,Proxy + - DOMAIN-SUFFIX,playstation.com,Proxy + - DOMAIN-SUFFIX,playstation.com.hk,Proxy + - DOMAIN-SUFFIX,playstation.net,Proxy + - DOMAIN-SUFFIX,playstationnetwork.com,Proxy + - DOMAIN-SUFFIX,pushwoosh.com,Proxy + - DOMAIN-SUFFIX,rime.im,Proxy + - DOMAIN-SUFFIX,servebom.com,Proxy + - DOMAIN-SUFFIX,sfx.ms,Proxy + - DOMAIN-SUFFIX,shadowsocks.org,Proxy + - DOMAIN-SUFFIX,sharethis.com,Proxy + - DOMAIN-SUFFIX,shazam.com,Proxy + - DOMAIN-SUFFIX,skype.com,Proxy + - DOMAIN-SUFFIX,smartdnsProxy.com,Proxy + - DOMAIN-SUFFIX,smartmailcloud.com,Proxy + - DOMAIN-SUFFIX,sndcdn.com,Proxy + - DOMAIN-SUFFIX,sony.com,Proxy + - DOMAIN-SUFFIX,soundcloud.com,Proxy + - DOMAIN-SUFFIX,sourceforge.net,Proxy + - DOMAIN-SUFFIX,spotify.com,Proxy + - DOMAIN-SUFFIX,squarespace.com,Proxy + - DOMAIN-SUFFIX,sstatic.net,Proxy + - DOMAIN-SUFFIX,st.luluku.pw,Proxy + - DOMAIN-SUFFIX,stackoverflow.com,Proxy + - DOMAIN-SUFFIX,startpage.com,Proxy + - DOMAIN-SUFFIX,staticflickr.com,Proxy + - DOMAIN-SUFFIX,steamcommunity.com,Proxy + - DOMAIN-SUFFIX,symauth.com,Proxy + - DOMAIN-SUFFIX,symcb.com,Proxy + - DOMAIN-SUFFIX,symcd.com,Proxy + - DOMAIN-SUFFIX,tapbots.com,Proxy + - DOMAIN-SUFFIX,tapbots.net,Proxy + - DOMAIN-SUFFIX,tdesktop.com,Proxy + - DOMAIN-SUFFIX,techcrunch.com,Proxy + - DOMAIN-SUFFIX,techsmith.com,Proxy + - DOMAIN-SUFFIX,thepiratebay.org,Proxy + - DOMAIN-SUFFIX,theverge.com,Proxy + - DOMAIN-SUFFIX,time.com,Proxy + - DOMAIN-SUFFIX,timeinc.net,Proxy + - DOMAIN-SUFFIX,tiny.cc,Proxy + - DOMAIN-SUFFIX,tinypic.com,Proxy + - DOMAIN-SUFFIX,tmblr.co,Proxy + - DOMAIN-SUFFIX,todoist.com,Proxy + - DOMAIN-SUFFIX,trello.com,Proxy + - DOMAIN-SUFFIX,trustasiassl.com,Proxy + - DOMAIN-SUFFIX,tumblr.co,Proxy + - DOMAIN-SUFFIX,tumblr.com,Proxy + - DOMAIN-SUFFIX,tweetdeck.com,Proxy + - DOMAIN-SUFFIX,tweetmarker.net,Proxy + - DOMAIN-SUFFIX,twitch.tv,Proxy + - DOMAIN-SUFFIX,txmblr.com,Proxy + - DOMAIN-SUFFIX,typekit.net,Proxy + - DOMAIN-SUFFIX,ubertags.com,Proxy + - DOMAIN-SUFFIX,ublock.org,Proxy + - DOMAIN-SUFFIX,ubnt.com,Proxy + - DOMAIN-SUFFIX,ulyssesapp.com,Proxy + - DOMAIN-SUFFIX,urchin.com,Proxy + - DOMAIN-SUFFIX,usertrust.com,Proxy + - DOMAIN-SUFFIX,v.gd,Proxy + - DOMAIN-SUFFIX,v2ex.com,Proxy + - DOMAIN-SUFFIX,vimeo.com,Proxy + - DOMAIN-SUFFIX,vimeocdn.com,Proxy + - DOMAIN-SUFFIX,vine.co,Proxy + - DOMAIN-SUFFIX,vivaldi.com,Proxy + - DOMAIN-SUFFIX,vox-cdn.com,Proxy + - DOMAIN-SUFFIX,vsco.co,Proxy + - DOMAIN-SUFFIX,vultr.com,Proxy + - DOMAIN-SUFFIX,w.org,Proxy + - DOMAIN-SUFFIX,w3schools.com,Proxy + - DOMAIN-SUFFIX,webtype.com,Proxy + - DOMAIN-SUFFIX,wikiwand.com,Proxy + - DOMAIN-SUFFIX,wikileaks.org,Proxy + - DOMAIN-SUFFIX,wikimedia.org,Proxy + - DOMAIN-SUFFIX,wikipedia.com,Proxy + - DOMAIN-SUFFIX,wikipedia.org,Proxy + - DOMAIN-SUFFIX,windows.com,Proxy + - DOMAIN-SUFFIX,windows.net,Proxy + - DOMAIN-SUFFIX,wire.com,Proxy + - DOMAIN-SUFFIX,wordpress.com,Proxy + - DOMAIN-SUFFIX,workflowy.com,Proxy + - DOMAIN-SUFFIX,wp.com,Proxy + - DOMAIN-SUFFIX,wsj.com,Proxy + - DOMAIN-SUFFIX,wsj.net,Proxy + - DOMAIN-SUFFIX,xda-developers.com,Proxy + - DOMAIN-SUFFIX,xeeno.com,Proxy + - DOMAIN-SUFFIX,xiti.com,Proxy + - DOMAIN-SUFFIX,yahoo.com,Proxy + - DOMAIN-SUFFIX,yimg.com,Proxy + - DOMAIN-SUFFIX,ying.com,Proxy + - DOMAIN-SUFFIX,yoyo.org,Proxy + - DOMAIN-SUFFIX,ytimg.com,Proxy + + # Telegram + - DOMAIN-SUFFIX,telegra.ph,Proxy + - DOMAIN-SUFFIX,telegram.org,Proxy + + - IP-CIDR,91.108.4.0/22,Proxy,no-resolve + - IP-CIDR,91.108.8.0/22,Proxy,no-resolve + - IP-CIDR,91.108.12.0/22,Proxy,no-resolve + - IP-CIDR,91.108.16.0/22,Proxy,no-resolve + - IP-CIDR,91.108.56.0/22,Proxy,no-resolve + - IP-CIDR,149.154.160.0/22,Proxy,no-resolve + - IP-CIDR,149.154.164.0/22,Proxy,no-resolve + - IP-CIDR,149.154.168.0/22,Proxy,no-resolve + - IP-CIDR,149.154.172.0/22,Proxy,no-resolve + + # LAN + - DOMAIN-SUFFIX,local,DIRECT + - IP-CIDR,127.0.0.0/8,DIRECT + - IP-CIDR,172.16.0.0/12,DIRECT + - IP-CIDR,192.168.0.0/16,DIRECT + - IP-CIDR,10.0.0.0/8,DIRECT + - IP-CIDR,17.0.0.0/8,DIRECT + - IP-CIDR,100.64.0.0/10,DIRECT + + # 最终规则 + - GEOIP,CN,DIRECT + - MATCH,Proxy diff --git a/routes/api.php b/routes/api.php index f669b5b2..71db92b0 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,7 +1,7 @@ 'Api\WebApi', 'middleware' => 'webApi'], function () { +Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi', 'domain' => sysConfig('web_api_url') ?? sysConfig('website_url')], function () { // ss后端WEBAPI V1版 Route::group(['prefix' => 'ss/v1'], function () { Route::get('node/{node}', 'SSController@getNodeInfo'); // 获取节点信息 @@ -71,18 +71,24 @@ Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi'], function ( }); // 客户端API -Route::group(['namespace' => 'Api\Client', 'prefix' => 'client/v1'], function () { - Route::get('config', 'V1Controller@getConfig'); // 获取配置 - Route::post('login', 'V1Controller@login'); // 登录 - Route::post('register', 'V1Controller@register'); // 注册 - Route::get('logout', 'V1Controller@logout'); // 退出 - Route::get('refresh', 'V1Controller@refresh'); // 刷新令牌 - Route::get('profile', 'V1Controller@userProfile'); // 获取账户信息 - Route::get('nodes', 'V1Controller@nodeList'); // 获取账户全部节点 - Route::get('node/{id}', 'V1Controller@nodeList'); // 获取账户个别节点 - Route::get('shop', 'V1Controller@shop'); // 获取商品信息 - Route::get('gift', 'V1Controller@gift'); // 获取邀请信息 - Route::post('checkIn', 'V1Controller@checkIn'); // 签到 - Route::post('payment/purchase', 'V1Controller@purchase'); // 获取商品信息 - Route::get('payment/getStatus', 'V1Controller@getStatus'); // 获取商品信息 + +Route::group(['namespace' => 'Api\Client', 'prefix' => 'v1'], function () { + Route::post('login', 'AuthController@login'); // 登录 + Route::post('register', 'AuthController@register'); // 注册 + Route::get('logout', 'AuthController@logout'); //登出 + Route::get('getconfig', 'ClientController@getConfig'); // 获取配置文件 + Route::get('version/update', 'ClientController@checkClientVersion'); // 检查更新 + Route::get('shop', 'ClientController@shop'); // 获取商品列表 + Route::group(['middleware' => 'auth.client'], function () { // 用户验证 + Route::get('getclash', 'ClientController@downloadProxies'); // 下载节点配置 + Route::get('getuserinfo', 'ClientController@getUserInfo'); // 获取用户信息 + Route::post('doCheckIn', 'ClientController@checkIn'); // 签到 + Route::get('checkClashUpdate', 'ClientController@proxyCheck'); // 判断是否更新订阅 + Route::get('proxy', 'ClientController@getProxyList'); // 获取节点列表 + Route::get('tickets', 'ClientController@ticketList'); // 获取工单列表 + Route::post('tickets/add', 'ClientController@ticket_add'); // 提交工单 + Route::get('order', 'ClientController@getOrders'); // 获取订单列表 + Route::get('invite/gift', 'ClientController@getInvite'); // 获取邀请详情和列表 + Route::get('gettransfer', 'ClientController@getUserTransfer'); // 获取剩余流量 + }); });