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' => '
御宅
飛享雲端
: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'); // 获取剩余流量
+ });
});