mirror of
https://github.com/v2board/v2board.git
synced 2024-11-10 17:49:11 +08:00
commit
516b2626ae
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
@ -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,7 +38,43 @@ 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
|
||||
]);
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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, '保存失败');
|
||||
}
|
||||
$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');
|
||||
|
||||
}
|
||||
if (!$plan->update($params)) {
|
||||
DB::rollBack();
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
DB::commit();
|
||||
return response([
|
||||
'data' => $plan->save()
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
if (!Plan::create($params)) {
|
||||
abort(500, '创建失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
|
@ -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([
|
||||
|
@ -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 = [
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,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();
|
||||
|
@ -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信息') : '账号过期或未订阅'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
@ -58,7 +58,7 @@ class UserController extends Controller
|
||||
'transfer_enable',
|
||||
'last_login_at',
|
||||
'created_at',
|
||||
'enable',
|
||||
'banned',
|
||||
'is_admin',
|
||||
'remind_expire',
|
||||
'remind_traffic',
|
||||
|
@ -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',
|
||||
],
|
||||
];
|
||||
|
@ -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)://'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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' => '佣金状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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' => '一次性金额有误'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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' => '教程步骤不能为空'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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' => '佣金格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -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
app/Services/ServerService.php
Normal file
31
app/Services/ServerService.php
Normal file
@ -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
app/Services/UserService.php
Normal file
50
app/Services/UserService.php
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -232,7 +232,9 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
| V2board version
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The only modification by laravel config
|
||||
|
|
||||
*/
|
||||
|
||||
'version' => '1.1.2'
|
||||
'version' => '1.2'
|
||||
];
|
||||
|
@ -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
|
||||
|
@ -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`;
|
||||
|
2
public/assets/admin/antd.chunk.css
vendored
2
public/assets/admin/antd.chunk.css
vendored
File diff suppressed because one or more lines are too long
2
public/assets/admin/umi.css
vendored
2
public/assets/admin/umi.css
vendored
File diff suppressed because one or more lines are too long
2
public/assets/admin/umi.js
vendored
2
public/assets/admin/umi.js
vendored
File diff suppressed because one or more lines are too long
2
public/assets/user/antd.chunk.css
vendored
2
public/assets/user/antd.chunk.css
vendored
File diff suppressed because one or more lines are too long
2
public/assets/user/umi.css
vendored
2
public/assets/user/umi.css
vendored
File diff suppressed because one or more lines are too long
2
public/assets/user/umi.js
vendored
2
public/assets/user/umi.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user