Kaynağa Gözat

Merge pull request #71 from v2board/dev

1.2
tokumeikoi 5 yıl önce
ebeveyn
işleme
516b2626ae
36 değiştirilmiş dosya ile 381 ekleme ve 257 silme
  1. 0 53
      app/Console/Commands/CheckExpire.php
  2. 27 6
      app/Console/Commands/CheckOrder.php
  3. 38 2
      app/Console/Commands/ResetTraffic.php
  4. 1 2
      app/Console/Kernel.php
  5. 3 0
      app/Exceptions/Handler.php
  6. 7 2
      app/Http/Controllers/Admin/ConfigController.php
  7. 9 29
      app/Http/Controllers/Admin/MailController.php
  8. 24 12
      app/Http/Controllers/Admin/PlanController.php
  9. 2 7
      app/Http/Controllers/Admin/TutorialController.php
  10. 9 23
      app/Http/Controllers/Admin/UserController.php
  11. 12 3
      app/Http/Controllers/Client/ClientController.php
  12. 5 16
      app/Http/Controllers/Server/DeepbworkController.php
  13. 5 19
      app/Http/Controllers/Server/PoseidonController.php
  14. 53 26
      app/Http/Controllers/User/OrderController.php
  15. 3 1
      app/Http/Controllers/User/ServerController.php
  16. 5 4
      app/Http/Controllers/User/TutorialController.php
  17. 1 1
      app/Http/Controllers/User/UserController.php
  18. 1 1
      app/Http/Kernel.php
  19. 10 8
      app/Http/Requests/Admin/ConfigSave.php
  20. 2 2
      app/Http/Requests/Admin/OrderUpdate.php
  21. 16 10
      app/Http/Requests/Admin/PlanSave.php
  22. 12 7
      app/Http/Requests/Admin/TutorialSave.php
  23. 21 11
      app/Http/Requests/Admin/UserUpdate.php
  24. 1 1
      app/Http/Requests/User/OrderSave.php
  25. 31 0
      app/Services/ServerService.php
  26. 50 0
      app/Services/UserService.php
  27. 4 2
      config/app.php
  28. 9 9
      database/install.sql
  29. 20 0
      database/update.sql
  30. 0 0
      public/assets/admin/antd.chunk.css
  31. 0 0
      public/assets/admin/umi.css
  32. 0 0
      public/assets/admin/umi.js
  33. 0 0
      public/assets/user/antd.chunk.css
  34. 0 0
      public/assets/user/umi.css
  35. 0 0
      public/assets/user/umi.js
  36. 0 0
      resources/rules/default.clash.yaml

+ 0 - 53
app/Console/Commands/CheckExpire.php

@@ -1,53 +0,0 @@
-<?php
-
-namespace App\Console\Commands;
-
-use Illuminate\Console\Command;
-use App\Models\Order;
-use App\Models\User;
-
-class CheckExpire extends Command
-{
-    /**
-     * The name and signature of the console command.
-     *
-     * @var string
-     */
-    protected $signature = 'check:expire';
-
-    /**
-     * The console command description.
-     *
-     * @var string
-     */
-    protected $description = '过期检查';
-
-    /**
-     * Create a new command instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        parent::__construct();
-    }
-
-    /**
-     * Execute the console command.
-     *
-     * @return mixed
-     */
-    public function handle()
-    {
-        $users = User::all();
-        foreach ($users as $user) {
-            if ($user->expired_at < time() || $user->u + $user->d >= $user->transfer_enable) {
-                $user->enable = 0;
-            } else {
-                $user->enable = 1;
-            }
-            $user->save();
-        }
-    }
-
-}

+ 27 - 6
app/Console/Commands/CheckOrder.php

@@ -60,15 +60,18 @@ class CheckOrder extends Command
         }
     }
 
-    private function orderHandle($order)
+    private function orderHandle(Order $order)
     {
         $user = User::find($order->user_id);
-        return $this->buy($order, $user);
+        $plan = Plan::find($order->plan_id);
+        if ($order->cycle === 'onetime_price') {
+            return $this->buyByOneTime($order, $user, $plan);
+        }
+        return $this->buyByCycle($order, $user, $plan);
     }
 
-    private function buy($order, $user)
+    private function buyByCycle(Order $order, User $user, Plan $plan)
     {
-        $plan = Plan::find($order->plan_id);
         // change plan process
         if ($order->type == 3) {
             $user->expired_at = time();
@@ -77,12 +80,30 @@ class CheckOrder extends Command
             $user->balance = $user->balance + $order->refund_amount;
         }
         $user->transfer_enable = $plan->transfer_enable * 1073741824;
-        $user->enable = 1;
+        if ((int)config('v2board.renew_reset_traffic_enable', 1)) {
+            $user->u = 0;
+            $user->d = 0;
+        }
+        $user->plan_id = $plan->id;
+        $user->group_id = $plan->group_id;
+        $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
+        if ($user->save()) {
+            $order->status = 3;
+            $order->save();
+        }
+    }
+
+    private function buyByOneTime(Order $order, User $user, Plan $plan)
+    {
+        if ($order->refund_amount) {
+            $user->balance = $user->balance + $order->refund_amount;
+        }
+        $user->transfer_enable = $plan->transfer_enable * 1073741824;
         $user->u = 0;
         $user->d = 0;
         $user->plan_id = $plan->id;
         $user->group_id = $plan->group_id;
-        $user->expired_at = $this->getTime($order->cycle, $user->expired_at);
+        $user->expired_at = NULL;
         if ($user->save()) {
             $order->status = 3;
             $order->save();

+ 38 - 2
app/Console/Commands/ResetTraffic.php

@@ -3,7 +3,7 @@
 namespace App\Console\Commands;
 
 use Illuminate\Console\Command;
-use Illuminate\Support\Facades\DB;
+use App\Models\User;
 
 class ResetTraffic extends Command
 {
@@ -38,9 +38,45 @@ class ResetTraffic extends Command
      */
     public function handle()
     {
-        DB::table('v2_user')->update([
+        $user = User::where('expired_at', '!=', NULL);
+        $resetTrafficMethod = config('v2board.reset_traffic_method', 0);
+        switch ((int)$resetTrafficMethod) {
+            // 1 a month
+            case 0:
+                $this->resetByMonthFirstDay($user);
+                break;
+            // expire day
+            case 1:
+                $this->resetByExpireDay($user);
+                break;
+        }
+    }
+
+    private function resetByMonthFirstDay(User $user):void
+    {
+        $user->update([
             'u' => 0,
             'd' => 0
         ]);
     }
+
+    private function resetByExpireDay(User $user):void
+    {
+        $date = date('Y-m-d', time());
+        $startAt = strtotime((string)$date);
+        $endAt = (int)$startAt + 24 * 3600;
+        $lastDay = date('d', strtotime('last day of +0 months'));
+        if ((string)$lastDay === '29') {
+            $endAt = (int)$startAt + 72 * 3600;
+        }
+        if ((string)$lastDay === '30') {
+            $endAt = (int)$startAt + 48 * 3600;
+        }
+        $user->where('expired_at', '>=', (int)$startAt)
+            ->where('expired_at', '<', (int)$endAt)
+            ->update([
+                'u' => 0,
+                'd' => 0
+            ]);
+    }
 }

+ 1 - 2
app/Console/Kernel.php

@@ -28,10 +28,9 @@ class Kernel extends ConsoleKernel
         $schedule->command('v2board:cache')->hourly();
         // check
         $schedule->command('check:order')->everyMinute();
-        $schedule->command('check:expire')->everyMinute();
         $schedule->command('check:commission')->everyMinute();
         // reset
-        $schedule->command('reset:traffic')->monthly();
+        $schedule->command('reset:traffic')->daily();
         $schedule->command('reset:serverLog')->monthly();
         // send
         $schedule->command('send:remindMail')->dailyAt('11:30');

+ 3 - 0
app/Exceptions/Handler.php

@@ -46,6 +46,9 @@ class Handler extends ExceptionHandler
      */
     public function render($request, Exception $exception)
     {
+        if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
+            abort(429, '请求频繁,请稍后再试');
+        }
         return parent::render($request, $exception);
     }
 

+ 7 - 2
app/Http/Controllers/Admin/ConfigController.php

@@ -28,12 +28,16 @@ class ConfigController extends Controller
                     'app_description' => config('v2board.app_description', 'V2Board is best!'),
                     'app_url' => config('v2board.app_url'),
                     'subscribe_url' => config('v2board.subscribe_url'),
-                    'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
                     'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
                     'try_out_hour' => (int)config('v2board.try_out_hour', 1),
                     'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
                     'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT)
                 ],
+                'subscribe' => [
+                    'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
+                    'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
+                    'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1)
+                ],
                 'pay' => [
                     // alipay
                     'alipay_enable' => (int)config('v2board.alipay_enable'),
@@ -46,6 +50,7 @@ class ConfigController extends Controller
                     'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable'),
                     'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable'),
                     'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
+                    'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
                     // bitpayx
                     'bitpayx_enable' => config('v2board.bitpayx_enable'),
                     'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
@@ -76,7 +81,7 @@ class ConfigController extends Controller
         $data = $request->input();
         $array = \Config::get('v2board');
         foreach ($data as $k => $v) {
-            if (!in_array($k, ConfigSave::filter())) {
+            if (!in_array($k, array_keys(ConfigSave::RULES))) {
                 abort(500, '参数' . $k . '不在规则内,禁止修改');
             }
             $array[$k] = $v;

+ 9 - 29
app/Http/Controllers/Admin/MailController.php

@@ -3,24 +3,27 @@
 namespace App\Http\Controllers\Admin;
 
 use App\Http\Requests\Admin\MailSend;
+use App\Services\UserService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
-use App\Models\User;
 use App\Jobs\SendEmail;
 
 class MailController extends Controller
 {
     public function send(MailSend $request)
     {
-
+        $userService = new UserService();
+        $users = [];
         switch ($request->input('type')) {
-            case 1: $users = $this->getAllUser();
+            case 1: $users = $userService->getAllUsers();
             break;
-            case 2: $users = $this->getReceiver($request->input('receiver'));
+            case 2: $users = $userService->getUsersByIds($request->input('receiver'));
             break;
-            case 3: $users = $this->getSubscribeUser();
+            // available users
+            case 3: $users = $userService->getAvailableUsers();
             break;
-            case 4: $users = $this->getExpireUser();
+            // un available users
+            case 4: $users = $userService->getUnAvailbaleUsers();
             break;
         }
 
@@ -41,27 +44,4 @@ class MailController extends Controller
             'data' => true
         ]);
     }
-
-    private function getAllUser()
-    {
-        return User::all();
-    }
-
-    private function getReceiver($receiver)
-    {
-        if (empty($receiver)) {
-            abort(500, '收件人不能为空');
-        }
-        return User::whereIn('id', $receiver)->get();
-    }
-
-    private function getSubscribeUser()
-    {
-        return User::where('expired_at', '=>', time())->get();
-    }
-
-    private function getExpireUser()
-    {
-        return User::where('expired_at', '<', time())->get();
-    }
 }

+ 24 - 12
app/Http/Controllers/Admin/PlanController.php

@@ -9,6 +9,7 @@ use App\Http\Controllers\Controller;
 use App\Models\Plan;
 use App\Models\Order;
 use App\Models\User;
+use Illuminate\Support\Facades\DB;
 
 class PlanController extends Controller
 {
@@ -21,25 +22,36 @@ class PlanController extends Controller
 
     public function save(PlanSave $request)
     {
+        $params = $request->only(array_keys(PlanSave::RULES));
         if ($request->input('id')) {
             $plan = Plan::find($request->input('id'));
             if (!$plan) {
                 abort(500, '该订阅不存在');
             }
-        } else {
-            $plan = new Plan();
+            DB::beginTransaction();
+            if (isset($params->group_id) && ($params->group_id !== $plan->group_id)) {
+                if (!User::where('plan_id', $plan->id)
+                    ->get()
+                    ->update(['group_id', $plan->group_id])
+                ) {
+                    DB::rollBack();
+                    abort(500, '保存失败');
+                }
+            }
+            if (!$plan->update($params)) {
+                DB::rollBack();
+                abort(500, '保存失败');
+            }
+            DB::commit();
+            return response([
+                'data' => true
+            ]);
+        }
+        if (!Plan::create($params)) {
+            abort(500, '创建失败');
         }
-        $plan->name = $request->input('name');
-        $plan->content = $request->input('content');
-        $plan->transfer_enable = $request->input('transfer_enable');
-        $plan->group_id = $request->input('group_id');
-        $plan->month_price = $request->input('month_price');
-        $plan->quarter_price = $request->input('quarter_price');
-        $plan->half_year_price = $request->input('half_year_price');
-        $plan->year_price = $request->input('year_price');
-
         return response([
-            'data' => $plan->save()
+            'data' => true
         ]);
     }
 

+ 2 - 7
app/Http/Controllers/Admin/TutorialController.php

@@ -12,18 +12,13 @@ class TutorialController extends Controller
     public function fetch(Request $request)
     {
         return response([
-            'data' => Tutorial::all()
+            'data' => Tutorial::get()
         ]);
     }
 
     public function save(TutorialSave $request)
     {
-        $params = $request->only([
-            'title',
-            'description',
-            'steps',
-            'icon'
-        ]);
+        $params = $request->only(array_keys(TutorialSave::RULES));
 
         if (!$request->input('id')) {
             if (!Tutorial::create($params)) {

+ 9 - 23
app/Http/Controllers/Admin/UserController.php

@@ -59,41 +59,27 @@ class UserController extends Controller
 
     public function update(UserUpdate $request)
     {
-        $updateData = $request->only([
-            'email',
-            'password',
-            'transfer_enable',
-            'expired_at',
-            'banned',
-            'plan_id',
-            'commission_rate',
-            'discount',
-            'is_admin',
-            'u',
-            'd',
-            'balance',
-            'commission_balance'
-        ]);
+        $params = $request->only(array_keys(UserUpdate::RULES));
         $user = User::find($request->input('id'));
         if (!$user) {
             abort(500, '用户不存在');
         }
-        if (User::where('email', $updateData['email'])->first() && $user->email !== $updateData['email']) {
+        if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
             abort(500, '邮箱已被使用');
         }
-        if (isset($updateData['password'])) {
-            $updateData['password'] = password_hash($updateData['password'], PASSWORD_DEFAULT);
+        if (isset($params['password'])) {
+            $params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
         } else {
-            unset($updateData['password']);
+            unset($params['password']);
         }
-        if (isset($updateData['plan_id'])) {
-            $plan = Plan::find($updateData['plan_id']);
+        if (isset($params['plan_id'])) {
+            $plan = Plan::find($params['plan_id']);
             if (!$plan) {
                 abort(500, '订阅计划不存在');
             }
-            $updateData['group_id'] = $plan->group_id;
+            $params['group_id'] = $plan->group_id;
         }
-        if (!$user->update($updateData)) {
+        if (!$user->update($params)) {
             abort(500, '保存失败');
         }
         return response([

+ 12 - 3
app/Http/Controllers/Client/ClientController.php

@@ -3,10 +3,12 @@
 namespace App\Http\Controllers\Client;
 
 use App\Http\Controllers\Controller;
+use App\Http\Middleware\User;
 use Illuminate\Http\Request;
 use App\Models\Server;
 use App\Utils\Helper;
 use Symfony\Component\Yaml\Yaml;
+use App\Services\UserService;
 
 class ClientController extends Controller
 {
@@ -15,7 +17,8 @@ class ClientController extends Controller
         $user = $request->user;
         $server = [];
         // account not expired and is not banned.
-        if ($user->expired_at > time() && !$user->banned) {
+        $userService = new UserService();
+        if ($userService->isAvailable($user)) {
             $servers = Server::where('show', 1)
                 ->orderBy('name')
                 ->get();
@@ -137,11 +140,17 @@ class ClientController extends Controller
         array_push($proxyGroup, [
             'name' => 'select',
             'type' => 'select',
-            'proxies' => $proxies
+            'proxies' => array_merge($proxies, [
+                'auto',
+                'fallback-auto'
+            ])
         ]);
 
         try {
-            $rules = Yaml::parseFile(base_path() . '/resources/rules/clash.rule.yaml')['Rule'];
+            $rules = [];
+            foreach (glob(base_path() . '/resources/rules/' . '*.clash.yaml') as $file) {
+                $rules = array_merge($rules, Yaml::parseFile($file)['Rule']);
+            }
         } catch (\Exception $e) {}
 
         $config = [

+ 5 - 16
app/Http/Controllers/Server/DeepbworkController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers\Server;
 
+use App\Services\ServerService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\User;
@@ -34,20 +35,8 @@ class DeepbworkController extends Controller
             abort(500, 'fail');
         }
         Cache::put('server_last_check_at_' . $server->id, time());
-        $users = User::whereIn('group_id', json_decode($server->group_id))
-            ->select([
-                'id',
-                'email',
-                't',
-                'u',
-                'd',
-                'transfer_enable',
-                'enable',
-                'v2ray_uuid',
-                'v2ray_alter_id',
-                'v2ray_level'
-            ])
-            ->get();
+        $serverService = new ServerService();
+        $users = $serverService->getAvailableUsers(json_decode($server->group_id));
         $result = [];
         foreach ($users as $user) {
             $user->v2ray_user = [
@@ -146,7 +135,7 @@ class DeepbworkController extends Controller
         if ($server->rules) {
             $rules = json_decode($server->rules);
             // domain
-            if (isset($rules->domain)) {
+            if (isset($rules->domain) && !empty($rules->domain)) {
                 $domainObj = new \StdClass();
                 $domainObj->type = 'field';
                 $domainObj->domain = $rules->domain;
@@ -154,7 +143,7 @@ class DeepbworkController extends Controller
                 array_push($json->routing->rules, $domainObj);
             }
             // protocol
-            if (isset($rules->protocol)) {
+            if (isset($rules->protocol) && !empty($rules->protocol)) {
                 $protocolObj = new \StdClass();
                 $protocolObj->type = 'field';
                 $protocolObj->protocol = $rules->protocol;

+ 5 - 19
app/Http/Controllers/Server/PoseidonController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers\Server;
 
+use App\Services\ServerService;
 use Illuminate\Http\Request;
 use App\Http\Controllers\Controller;
 use App\Models\User;
@@ -25,24 +26,9 @@ class PoseidonController extends Controller
         if (!$server) {
             return $this->error("server could not be found", 404);
         }
-
         Cache::put('server_last_check_at_' . $server->id, time());
-        $users = User::whereIn('group_id', json_decode($server->group_id))
-            ->select([
-                'id',
-                'email',
-                't',
-                'u',
-                'd',
-                'transfer_enable',
-                'enable',
-                'v2ray_uuid',
-                'v2ray_alter_id',
-                'v2ray_level'
-            ])
-            ->whereRaw('u + d < transfer_enable')
-            ->where('enable', 1)
-            ->get();
+        $serverService = new ServerService();
+        $users = $serverService->getAvailableUsers(json_decode($server->group_id));
         $result = [];
         foreach ($users as $user) {
             $user->v2ray_user = [
@@ -137,7 +123,7 @@ class PoseidonController extends Controller
         if ($server->rules) {
             $rules = json_decode($server->rules);
             // domain
-            if (isset($rules->domain)) {
+            if (isset($rules->domain) && !empty($rules->domain)) {
                 $domainObj = new \StdClass();
                 $domainObj->type = 'field';
                 $domainObj->domain = $rules->domain;
@@ -145,7 +131,7 @@ class PoseidonController extends Controller
                 array_push($json->routing->rules, $domainObj);
             }
             // protocol
-            if (isset($rules->protocol)) {
+            if (isset($rules->protocol) && !empty($rules->protocol)) {
                 $protocolObj = new \StdClass();
                 $protocolObj->type = 'field';
                 $protocolObj->protocol = $rules->protocol;

+ 53 - 26
app/Http/Controllers/User/OrderController.php

@@ -75,18 +75,43 @@ class OrderController extends Controller
     private function getSurplusValue(User $user)
     {
         $plan = Plan::find($user->plan_id);
+        switch ($plan->type) {
+            case 0: return $this->getSurplusValueByCycle($user, $plan);
+            case 1: return $this->getSurplusValueByOneTime($user, $plan);
+        }
+    }
+
+    private function getSurplusValueByOneTime(User $user, Plan $plan)
+    {
+        $trafficUnitPrice = 0;
+        $trafficUnitPrice = $plan->onetime_price / $plan->transfer_enable;
+        if ($user->discount && $trafficUnitPrice) {
+            $trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100);
+        }
+        $notUsedTrafficPrice = $plan->transfer_enable - (($user->u + $user->d) / 1073741824);
+        $result = $trafficUnitPrice * $notUsedTrafficPrice;
+        return $result > 0 ? $result : 0;
+    }
+
+    private function getSurplusValueByCycle(User $user, Plan $plan)
+    {
         $dayPrice = 0;
         if ($plan->month_price) {
-            $dayPrice = $plan->month_price / 30;
+            $dayPrice = $plan->month_price / 2592000;
         } else if ($plan->quarter_price) {
-            $dayPrice = $plan->quarter_price / 91;
+            $dayPrice = $plan->quarter_price / 7862400;
         } else if ($plan->half_year_price) {
-            $dayPrice = $plan->half_year_price / 183;
+            $dayPrice = $plan->half_year_price / 15811200;
         } else if ($plan->year_price) {
-            $dayPrice = $plan->year_price / 365;
+            $dayPrice = $plan->year_price / 31536000;
         }
-        $remainingDay = ($user->expired_at - time()) / 86400;
-        return $remainingDay * $dayPrice;
+        // exclude discount
+        if ($user->discount && $dayPrice) {
+            $dayPrice = $dayPrice - ($dayPrice * $user->discount / 100);
+        }
+        $remainingDay = $user->expired_at - time();
+        $result = $remainingDay * $dayPrice;
+        return $result > 0 ? $result : 0;
     }
 
     public function save(OrderSave $request)
@@ -137,22 +162,6 @@ class OrderController extends Controller
         $order->cycle = $request->input('cycle');
         $order->trade_no = Helper::guid();
         $order->total_amount = $plan[$request->input('cycle')];
-        // renew and change subscribe process
-        if ($user->expired_at > time() && $order->plan_id !== $user->plan_id) {
-            if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系管理员');
-            $order->type = 3;
-            $order->surplus_amount = $this->getSurplusValue($user);
-            if ($order->surplus_amount >= $order->total_amount) {
-                $order->refund_amount = $order->surplus_amount - $order->total_amount;
-                $order->total_amount = 0;
-            } else {
-                $order->total_amount = $order->total_amount - $order->surplus_amount;
-            }
-        } else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
-            $order->type = 2;
-        } else {
-            $order->type = 1;
-        }
         // discount start
         // coupon
         if (isset($coupon)) {
@@ -179,6 +188,22 @@ class OrderController extends Controller
         // discount complete
         $order->total_amount = $order->total_amount - $order->discount_amount;
         // discount end
+        // renew and change subscribe process
+        if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
+            if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单');
+            $order->type = 3;
+            $order->surplus_amount = $this->getSurplusValue($user);
+            if ($order->surplus_amount >= $order->total_amount) {
+                $order->refund_amount = $order->surplus_amount - $order->total_amount;
+                $order->total_amount = 0;
+            } else {
+                $order->total_amount = $order->total_amount - $order->surplus_amount;
+            }
+        } else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
+            $order->type = 2;
+        } else {
+            $order->type = 1;
+        }
         // invite process
         if ($user->invite_user_id && $order->total_amount > 0) {
             $order->invite_user_id = $user->invite_user_id;
@@ -381,14 +406,15 @@ class OrderController extends Controller
 
     private function stripeAlipay($order)
     {
-        $exchange = Helper::exchange('CNY', 'HKD');
+        $currency = config('stripe_currency', 'hkd');
+        $exchange = Helper::exchange('CNY', strtoupper($currency));
         if (!$exchange) {
             abort(500, '货币转换超时,请稍后再试');
         }
         Stripe::setApiKey(config('v2board.stripe_sk_live'));
         $source = Source::create([
             'amount' => floor($order->total_amount * $exchange),
-            'currency' => 'hkd',
+            'currency' => $currency,
             'type' => 'alipay',
             'redirect' => [
                 'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'
@@ -406,14 +432,15 @@ class OrderController extends Controller
 
     private function stripeWepay($order)
     {
-        $exchange = Helper::exchange('CNY', 'HKD');
+        $currency = config('stripe_currency', 'hkd');
+        $exchange = Helper::exchange('CNY', strtoupper($currency));
         if (!$exchange) {
             abort(500, '货币转换超时,请稍后再试');
         }
         Stripe::setApiKey(config('v2board.stripe_sk_live'));
         $source = Source::create([
             'amount' => floor($order->total_amount * $exchange),
-            'currency' => 'hkd',
+            'currency' => $currency,
             'type' => 'wechat',
             'redirect' => [
                 'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order'

+ 3 - 1
app/Http/Controllers/User/ServerController.php

@@ -3,6 +3,7 @@
 namespace App\Http\Controllers\User;
 
 use App\Http\Controllers\Controller;
+use App\Services\UserService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Cache;
 use App\Models\Server;
@@ -17,7 +18,8 @@ class ServerController extends Controller
     {
         $user = User::find($request->session()->get('id'));
         $server = [];
-        if ($user->expired_at > time()) {
+        $userService = new UserService();
+        if ($userService->isAvailable($user)) {
             $servers = Server::where('show', 1)
                 ->orderBy('name')
                 ->get();

+ 5 - 4
app/Http/Controllers/User/TutorialController.php

@@ -49,9 +49,10 @@ class TutorialController extends Controller
                 'data' => $tutorial
             ]);
         }
-        $tutorial = Tutorial::select(['id', 'title', 'description', 'icon'])
+        $tutorial = Tutorial::select(['id', 'category_id', 'title', 'icon'])
             ->where('show', 1)
-            ->get();
+            ->get()
+            ->groupBy('category_id');
         $user = User::find($request->session()->get('id'));
         $response = [
             'data' => [
@@ -59,8 +60,8 @@ class TutorialController extends Controller
                 'safe_area_var' => [
                     'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'],
                     'app_name' => config('v2board.app_name', 'V2board'),
-                    'apple_id' => $user->expired_at > time() ? config('v2board.apple_id', '管理员暂无提供AppleID信息') : '账号过期或未订阅',
-                    'apple_id_password' => $user->expired_at > time() ? config('v2board.apple_id_password', '管理员暂无提供AppleID信息') : '账号过期或未订阅'
+                    'apple_id' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id', '本站暂无提供AppleID信息') : '账号过期或未订阅',
+                    'apple_id_password' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id_password', '本站暂无提供AppleID信息') : '账号过期或未订阅'
                 ]
             ]
         ];

+ 1 - 1
app/Http/Controllers/User/UserController.php

@@ -58,7 +58,7 @@ class UserController extends Controller
                 'transfer_enable',
                 'last_login_at',
                 'created_at',
-                'enable',
+                'banned',
                 'is_admin',
                 'remind_expire',
                 'remind_traffic',

+ 1 - 1
app/Http/Kernel.php

@@ -43,7 +43,7 @@ class Kernel extends HttpKernel
             \Illuminate\Session\Middleware\StartSession::class,
             \App\Http\Middleware\ForceJson::class,
             \App\Http\Middleware\CORS::class,
-            'throttle:60,1',
+            'throttle:120,1',
             'bindings',
         ],
     ];

+ 10 - 8
app/Http/Requests/Admin/ConfigSave.php

@@ -16,14 +16,17 @@ class ConfigSave extends FormRequest
         'email_verify' => 'in:0,1',
         'app_name' => '',
         'app_description' => '',
-        'app_url' => 'url',
-        'subscribe_url' => 'url',
-        'plan_change_enable' => 'in:0,1',
+        'app_url' => 'nullable|url',
+        'subscribe_url' => 'nullable|url',
         'try_out_enable' => 'in:0,1',
         'try_out_plan_id' => 'integer',
         'try_out_hour' => 'numeric',
         'email_whitelist_enable' => 'in:0,1',
         'email_whitelist_suffix' => '',
+        // subscribe
+        'plan_change_enable' => 'in:0,1',
+        'reset_traffic_method' => 'in:0,1',
+        'renew_reset_traffic_enable' => 'in:0,1',
         // server
         'server_token' => 'nullable|min:16',
         'server_license' => 'nullable',
@@ -38,6 +41,7 @@ class ConfigSave extends FormRequest
         'stripe_sk_live' => '',
         'stripe_pk_live' => '',
         'stripe_webhook_key' => '',
+        'stripe_currency' => 'in:hkd,usd,sgd',
         // bitpayx
         'bitpayx_enable' => 'in:0,1',
         'bitpayx_appsecret' => '',
@@ -55,11 +59,6 @@ class ConfigSave extends FormRequest
         'apple_id_password' => ''
     ];
 
-    public static function filter()
-    {
-        return array_keys(self::RULES);
-    }
-
     /**
      * Get the validation rules that apply to the request.
      *
@@ -72,7 +71,10 @@ class ConfigSave extends FormRequest
 
     public function messages()
     {
+        // illiteracy prompt
         return [
+            'app_url.url' => '站点URL格式不正确,必须携带http(s)://',
+            'subscribe_url.url' => '订阅URL格式不正确,必须携带http(s)://'
         ];
     }
 }

+ 2 - 2
app/Http/Requests/Admin/OrderUpdate.php

@@ -15,7 +15,7 @@ class OrderUpdate extends FormRequest
     {
         return [
             'status' => 'in:0,1,2,3',
-            'commission_status' => 'in:0,1'
+            'commission_status' => 'in:0,1,2'
         ];
     }
 
@@ -23,7 +23,7 @@ class OrderUpdate extends FormRequest
     {
         return [
             'status.in' => '销售状态格式不正确',
-            'commission_status.in' => '续费状态格式不正确'
+            'commission_status.in' => '佣金状态格式不正确'
         ];
     }
 }

+ 16 - 10
app/Http/Requests/Admin/PlanSave.php

@@ -6,6 +6,17 @@ use Illuminate\Foundation\Http\FormRequest;
 
 class PlanSave extends FormRequest
 {
+    CONST RULES = [
+        'name' => 'required',
+        'content' => '',
+        'group_id' => 'required',
+        'transfer_enable' => 'required',
+        'month_price' => 'nullable|integer',
+        'quarter_price' => 'nullable|integer',
+        'half_year_price' => 'nullable|integer',
+        'year_price' => 'nullable|integer',
+        'onetime_price' => 'nullable|integer'
+    ];
     /**
      * Get the validation rules that apply to the request.
      *
@@ -13,27 +24,22 @@ class PlanSave extends FormRequest
      */
     public function rules()
     {
-        return [
-            'name' => 'required',
-            'group_id' => 'required',
-            'transfer_enable' => 'required',
-            'month_price' => 'nullable|integer',
-            'quarter_price' => 'nullable|integer',
-            'half_year_price' => 'nullable|integer',
-            'year_price' => 'nullable|integer'
-        ];
+        return self::RULES;
     }
 
     public function messages()
     {
         return [
             'name.required' => '套餐名称不能为空',
+            'type.required' => '套餐类型不能为空',
+            'type.in' => '套餐类型格式有误',
             'group_id.required' => '权限组不能为空',
             'transfer_enable.required' => '流量不能为空',
             'month_price.integer' => '月付金额格式有误',
             'quarter_price.integer' => '季付金额格式有误',
             'half_year_price.integer' => '半年付金额格式有误',
-            'year_price.integer' => '年付金额格式有误'
+            'year_price.integer' => '年付金额格式有误',
+            'onetime_price.integer' => '一次性金额有误'
         ];
     }
 }

+ 12 - 7
app/Http/Requests/Admin/TutorialSave.php

@@ -6,6 +6,13 @@ use Illuminate\Foundation\Http\FormRequest;
 
 class TutorialSave extends FormRequest
 {
+    CONST RULES = [
+        'title' => 'required',
+        // 1:windows 2:macos 3:ios 4:android 5:linux 6:router
+        'category_id' => 'required|in:1,2,3,4,5,6',
+        'icon' => 'required',
+        'steps' => 'required'
+    ];
     /**
      * Get the validation rules that apply to the request.
      *
@@ -13,19 +20,17 @@ class TutorialSave extends FormRequest
      */
     public function rules()
     {
-        return [
-            'title' => 'required',
-            'description' => 'required',
-            'icon' => 'required'
-        ];
+        return self::RULES;
     }
 
     public function messages()
     {
         return [
             'title.required' => '标题不能为空',
-            'description.required' => '描述不能为空',
-            'icon.required' => '图标不能为空'
+            'category_id.required' => '分类不能为空',
+            'category_id.in' => '分类格式不正确',
+            'icon.required' => '图标不能为空',
+            'steps.required' => '教程步骤不能为空'
         ];
     }
 }

+ 21 - 11
app/Http/Requests/Admin/UserUpdate.php

@@ -6,6 +6,21 @@ use Illuminate\Foundation\Http\FormRequest;
 
 class UserUpdate extends FormRequest
 {
+    CONST RULES = [
+        'email' => 'required|email',
+        'password' => 'nullable',
+        'transfer_enable' => 'numeric',
+        'expired_at' => 'nullable|integer',
+        'banned' => 'required|in:0,1',
+        'plan_id' => 'nullable|integer',
+        'commission_rate' => 'nullable|integer|min:0|max:100',
+        'discount' => 'nullable|integer|min:0|max:100',
+        'is_admin' => 'required|in:0,1',
+        'u' => 'integer',
+        'd' => 'integer',
+        'balance' => 'integer',
+        'commission_balance' => 'integer'
+    ];
     /**
      * Get the validation rules that apply to the request.
      *
@@ -13,16 +28,7 @@ class UserUpdate extends FormRequest
      */
     public function rules()
     {
-        return [
-            'email' => 'required|email',
-            'transfer_enable' => 'numeric',
-            'expired_at' => 'integer',
-            'banned' => 'required|in:0,1',
-            'is_admin' => 'required|in:0,1',
-            'plan_id' => 'integer',
-            'commission_rate' => 'nullable|integer|min:0|max:100',
-            'discount' => 'nullable|integer|min:0|max:100'
-        ];
+        return self::RULES;
     }
 
     public function messages()
@@ -44,7 +50,11 @@ class UserUpdate extends FormRequest
             'discount.integer' => '专属折扣比例格式不正确',
             'discount.nullable' => '专属折扣比例格式不正确',
             'discount.min' => '专属折扣比例最小为0',
-            'discount.max' => '专属折扣比例最大为100'
+            'discount.max' => '专属折扣比例最大为100',
+            'u.integer' => '上行流量格式不正确',
+            'd.integer' => '下行流量格式不正确',
+            'balance.integer' => '余额格式不正确',
+            'commission_balance.integer' => '佣金格式不正确'
         ];
     }
 }

+ 1 - 1
app/Http/Requests/User/OrderSave.php

@@ -15,7 +15,7 @@ class OrderSave extends FormRequest
     {
         return [
             'plan_id' => 'required',
-            'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price'
+            'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price'
         ];
     }
 

+ 31 - 0
app/Services/ServerService.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\User;
+
+class ServerService
+{
+    public function getAvailableUsers($groupId)
+    {
+        return User::whereIn('group_id', $groupId)
+            ->whereRaw('u + d < transfer_enable')
+            ->where(function ($query) {
+                $query->where('expired_at', '>=', time())
+                    ->orWhere('expired_at', NULL);
+            })
+            ->where('banned', 0)
+            ->select([
+                'id',
+                'email',
+                't',
+                'u',
+                'd',
+                'transfer_enable',
+                'v2ray_uuid',
+                'v2ray_alter_id',
+                'v2ray_level'
+            ])
+            ->get();
+    }
+}

+ 50 - 0
app/Services/UserService.php

@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\User;
+
+class UserService
+{
+    public function isAvailable(User $user)
+    {
+        if (!$user->banned && $user->transfer_enable && ($user->expired_at > time() || $user->expired_at === NULL)) {
+            return true;
+        }
+        return false;
+    }
+
+    public function getAvailableUsers()
+    {
+        return User::whereRaw('u + d < transfer_enable')
+            ->where(function ($query) {
+                $query->where('expired_at', '>=', time())
+                    ->orWhere('expired_at', NULL);
+            })
+            ->where('banned', 0)
+            ->get();
+    }
+
+    public function getUnAvailbaleUsers()
+    {
+        return User::where(function ($query) {
+            $query->where('expired_at', '<', time())
+                ->orWhere('expired_at', 0);
+        })
+            ->where(function ($query) {
+            $query->where('plan_id', NULL)
+                ->orWhere('transfer_enable', 0);
+        })
+            ->get();
+    }
+
+    public function getUsersByIds($ids)
+    {
+        return User::whereIn('id', $ids)->get();
+    }
+
+    public function getAllUsers()
+    {
+        return User::all();
+    }
+}

+ 4 - 2
config/app.php

@@ -232,7 +232,9 @@ return [
     |--------------------------------------------------------------------------
     | V2board version
     |--------------------------------------------------------------------------
+    |
+    | The only modification by laravel config
+    |
     */
-
-    'version' => '1.1.2'
+    'version' => '1.2'
 ];

+ 9 - 9
database/install.sql

@@ -109,6 +109,7 @@ CREATE TABLE `v2_plan` (
   `quarter_price` int(11) DEFAULT '0',
   `half_year_price` int(11) DEFAULT '0',
   `year_price` int(11) DEFAULT '0',
+  `onetime_price` int(11) DEFAULT NULL,
   `created_at` int(11) NOT NULL,
   `updated_at` int(11) NOT NULL,
   PRIMARY KEY (`id`)
@@ -188,8 +189,8 @@ CREATE TABLE `v2_ticket_message` (
 DROP TABLE IF EXISTS `v2_tutorial`;
 CREATE TABLE `v2_tutorial` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
+  `category_id` int(11) NOT NULL,
   `title` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
-  `description` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
   `icon` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
   `steps` text,
   `show` tinyint(1) NOT NULL DEFAULT '0',
@@ -198,11 +199,11 @@ CREATE TABLE `v2_tutorial` (
   PRIMARY KEY (`id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
-INSERT INTO `v2_tutorial` (`id`, `title`, `description`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
-(1,	'Windows',	'兼容 Windows 7 以上的版本',	'fab fa-2x fa-windows',	'[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压,解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置,找到Http代理,选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]',	1,	1577972408,	1581777396),
-(2,	'Android',	'兼容 Android 6 以上的版本',	'fab fa-2x fa-android',	'[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点,点击右下角的V字按钮即可连接。</div>\"}]',	1,	1577972534,	1577984397),
-(3,	'macOS',	'兼容 Yosemite 以上的版本',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url:<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$app_name}}</code></div>\\n<div>Config Name:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]',	1,	1577979855,	1581951993),
-(4,	'iOS',	'兼容 iOS 9 以上的版本',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID:<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]',	1,	1577982016,	1577983283);
+INSERT INTO `v2_tutorial` (`id`, `category_id`, `title`, `icon`, `steps`, `show`, `created_at`, `updated_at`) VALUES
+(1,	1,	'V2rayN',	'fab fa-2x fa-windows',	'[{\"default_area\":\"<div><div>下载 V2rayN 客户端。</div><div>下载完成后解压,解压完成后运行V2rayN</div><div>运行时请右键,以管理员身份运行</div></div>\",\"download_url\":\"/downloads/V2rayN.zip\"},{\"default_area\":\"<div>点击订阅按钮,选择订阅设置点击添加,输入如下内容后点击确定保存</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UkcHNtERTnjLVS8.jpg\"},{\"default_area\":\"<div>点击订阅后,从服务器列表选择服务器</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/BgPGFQ3kCSuIRjJ.jpg\"},{\"default_area\":\"<div>点击参数设置,找到Http代理,选择PAC模式后按确定保存即启动代理。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/vnVykKEFT8Lzo3f.jpg\"}]',	1,	1577972408,	1577980882),
+(2,	4,	'V2rayNG',	'fab fa-2x fa-android',	'[{\"default_area\":\"<div>下载 V2rayNG 客户端。</div>\",\"safe_area\":\"\",\"download_url\":\"/downloads/V2rayNG.apk\"},{\"default_area\":\"<div>打开 V2rayNG 点击左上角的菜单图标打开侧边栏,随后点击 订阅设置,点击右上角的➕按钮新增订阅。</div><div>按照下方内容进行填写,填写完毕后点击右上角的☑️按钮。</div>\",\"safe_area\":\"<div>备注:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\\n<div>地址(url):<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\",\"download_url\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ghuVkTe6LBqRxSO.jpg\"},{\"default_area\":\"<div>再次从侧边栏进入 设置 页面,点击 路由模式 将其更改为 \\b绕过局域网及大陆地址。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/Tf1AGoXZuhJrwOq.jpg\"},{\"default_area\":\"<div>随后从侧边栏回到 配置文件 页面,点击右上角的省略号图标选择更新订阅。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/UtfPShQXupRmB4L.jpg\"},{\"img_url\":\"https://i.loli.net/2019/11/21/ZkbNsSrAg3m5Dny.jpg\",\"default_area\":\"<div>点击选择您需要的节点,点击右下角的V字按钮即可连接。</div>\"}]',	1,	1577972534,	1577981610),
+(3,	2,	'ClashX',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>下载 ClashX 客户端,安装后运行。</div>\",\"download_url\":\"/downloads/ClashX.dmg\",\"img_url\":\"https://i.loli.net/2019/11/20/uNGrjl2noCL1f5B.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+M(订阅快捷键),在弹出的窗口点击添加输入下方信息</div>\",\"safe_area\":\"<div>Url:<code onclick=\\\"safeAreaCopy(\'{{$subscribe_url}}\')\\\">{{$subscribe_url}}</code></div>\\n<div>Config Name:<code onclick=\\\"safeAreaCopy(\'{{$app_name}}\')\\\">{{$app_name}}</code></div>\",\"img_url\":\"https://i.loli.net/2019/11/20/8eB13mRbFuszwxg.jpg\"},{\"default_area\":\"<div>点击通知栏 ClashX 图标保持选中状态,按快捷键 ⌘+S(设置为系统代理快捷键),即连接完成</div>\"}]',	1,	1577979855,	1577981646),
+(4,	3,	'Shadowrocket',	'fab fa-2x fa-apple',	'[{\"default_area\":\"<div>iOS上使用请在iOS浏览器中打开本页</div>\"},{\"default_area\":\"<div>在 App Store 登录本站提供的美区 Apple ID 下载客户端。</div><div>为了保护您的隐私,请勿在手机设置里直接登录,仅在 App Store 登录即可。</div><div>登陆完成后点击下方下载会自动唤起下载。</div>\",\"safe_area\":\"<div>Apple ID:<code onclick=\\\"safeAreaCopy(\'{{$apple_id}}\')\\\">{{$apple_id}}</code></div><div>密码:<code onclick=\\\"safeAreaCopy(\'{{$apple_id_password}}\')\\\">点击复制密码</code></div>\",\"download_url\":\"https://apps.apple.com/us/app/shadowrocket/id932747118\",\"img_url\":\"https://i.loli.net/2019/11/21/5idkjJ61stWgREV.jpg\"},{\"default_area\":\"<div>待客户端安装完成后,点击下方一键订阅按钮会自动唤起并进行订阅</div>\",\"safe_area\":\"\",\"img_url\":\"https://i.loli.net/2019/11/21/ZcqlNMb3eg5Uhxd.jpg\",\"download_url\":\"shadowrocket://add/sub://{{$b64_subscribe_url}}?remark={{$app_name}}\"},{\"default_area\":\"<div>选择节点进行链接,首次链接过程授权窗口请一路允许。</div>\",\"img_url\":\"https://i.loli.net/2019/11/21/9Zdxksr7Ey6hjlm.jpg\"}]',	1,	1577982016,	1577983283);
 
 DROP TABLE IF EXISTS `v2_user`;
 CREATE TABLE `v2_user` (
@@ -219,7 +220,6 @@ CREATE TABLE `v2_user` (
   `u` bigint(20) NOT NULL DEFAULT '0',
   `d` bigint(20) NOT NULL DEFAULT '0',
   `transfer_enable` bigint(20) NOT NULL DEFAULT '0',
-  `enable` tinyint(1) NOT NULL DEFAULT '1',
   `banned` tinyint(1) NOT NULL DEFAULT '0',
   `is_admin` tinyint(1) NOT NULL DEFAULT '0',
   `last_login_at` int(11) DEFAULT NULL,
@@ -232,7 +232,7 @@ CREATE TABLE `v2_user` (
   `remind_expire` tinyint(4) DEFAULT '1',
   `remind_traffic` tinyint(4) DEFAULT '1',
   `token` char(32) NOT NULL,
-  `expired_at` bigint(20) NOT NULL DEFAULT '0',
+  `expired_at` bigint(20) DEFAULT '0',
   `created_at` int(11) NOT NULL,
   `updated_at` int(11) NOT NULL,
   PRIMARY KEY (`id`),
@@ -240,4 +240,4 @@ CREATE TABLE `v2_user` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 
--- 2020-02-17 15:11:16
+-- 2020-03-02 12:29:05

+ 20 - 0
database/update.sql

@@ -162,3 +162,23 @@ ADD `surplus_amount` int(11) NULL COMMENT '剩余价值' AFTER `discount_amount`
 
 ALTER TABLE `v2_order`
 ADD `refund_amount` int(11) NULL COMMENT '退款金额' AFTER `surplus_amount`;
+
+ALTER TABLE `v2_tutorial`
+ADD `category_id` int(11) NOT NULL AFTER `id`;
+
+ALTER TABLE `v2_tutorial`
+DROP `description`;
+
+ALTER TABLE `v2_plan`
+CHANGE `month_price` `month_price` int(11) NULL AFTER `content`,
+CHANGE `quarter_price` `quarter_price` int(11) NULL AFTER `month_price`,
+CHANGE `half_year_price` `half_year_price` int(11) NULL AFTER `quarter_price`,
+CHANGE `year_price` `year_price` int(11) NULL AFTER `half_year_price`,
+ADD `onetime_price` int(11) NULL AFTER `year_price`;
+
+ALTER TABLE `v2_user`
+DROP `enable`,
+ADD `banned` tinyint(1) NOT NULL DEFAULT '0' AFTER `transfer_enable`;
+
+ALTER TABLE `v2_user`
+CHANGE `expired_at` `expired_at` bigint(20) NULL DEFAULT '0' AFTER `token`;

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/admin/antd.chunk.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/admin/umi.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/admin/umi.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/user/antd.chunk.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/user/umi.css


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
public/assets/user/umi.js


+ 0 - 0
resources/rules/clash.rule.yaml → resources/rules/default.clash.yaml


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor