Merge pull request #71 from v2board/dev

1.2
This commit is contained in:
tokumeikoi 2020-03-05 02:16:29 +08:00 committed by GitHub
commit 516b2626ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 387 additions and 263 deletions

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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
]);
}
}

View File

@ -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');

View File

@ -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);
}

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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
]);
}

View File

@ -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)) {

View File

@ -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([

View File

@ -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 = [

View File

@ -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;

View File

@ -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;

View File

@ -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'

View File

@ -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();

View File

@ -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信息') : '账号过期或未订阅'
]
]
];

View File

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

View File

@ -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',
],
];

View File

@ -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)://'
];
}
}

View File

@ -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' => '佣金状态格式不正确'
];
}
}

View File

@ -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' => '一次性金额有误'
];
}
}

View File

@ -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' => '教程步骤不能为空'
];
}
}

View File

@ -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' => '佣金格式不正确'
];
}
}

View File

@ -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'
];
}

View 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();
}
}

View 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();
}
}

View File

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

View File

@ -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

View File

@ -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`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long