Merge pull request #484 from v2board/dev

1.5.3
This commit is contained in:
tokumeikoi 2021-10-03 22:19:10 +09:00 committed by GitHub
commit 32c539d2c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
127 changed files with 2203 additions and 1283 deletions

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\CommissionLog;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
@ -59,27 +60,66 @@ class CheckCommission extends Command
public function autoPayCommission() public function autoPayCommission()
{ {
$order = Order::where('commission_status', 1) $orders = Order::where('commission_status', 1)
->where('invite_user_id', '!=', NULL) ->where('invite_user_id', '!=', NULL)
->get(); ->get();
foreach ($order as $item) { foreach ($orders as $order) {
$inviter = User::find($item->invite_user_id);
if (!$inviter) continue;
if ((int)config('v2board.withdraw_close_enable', 0)) {
$inviter->balance = $inviter->balance + $item->commission_balance;
} else {
$inviter->commission_balance = $inviter->commission_balance + $item->commission_balance;
}
DB::beginTransaction(); DB::beginTransaction();
if ($inviter->save()) { if (!$this->payHandle($order->invite_user_id, $order)) {
$item->commission_status = 2; DB::rollBack();
if (!$item->save()) { continue;
DB::rollBack();
continue;
}
DB::commit();
} }
$order->commission_status = 2;
if (!$order->save()) {
DB::rollBack();
continue;
}
DB::commit();
} }
} }
public function payHandle($inviteUserId, Order $order)
{
if ((int)config('v2board.commission_distribution_enable', 0)) {
$level = 3;
$commissionShareLevels = [
0 => (int)config('v2board.commission_distribution_l1'),
1 => (int)config('v2board.commission_distribution_l2'),
2 => (int)config('v2board.commission_distribution_l3')
];
} else {
$level = 3;
$commissionShareLevels = [
0 => 100
];
}
for ($l = 0; $l < $level; $l++) {
$inviter = User::find($inviteUserId);
if (!$inviter) continue;
if (!isset($commissionShareLevels[$l])) continue;
$commissionBalance = $order->commission_balance * ($commissionShareLevels[$l] / 100);
if ((int)config('v2board.withdraw_close_enable', 0)) {
$inviter->balance = $inviter->balance + $commissionBalance;
} else {
$inviter->commission_balance = $inviter->commission_balance + $commissionBalance;
}
if (!$inviter->save()) {
DB::rollBack();
return false;
}
if (!CommissionLog::create([
'invite_user_id' => $inviteUserId,
'user_id' => $order->user_id,
'trade_no' => $order->trade_no,
'order_amount' => $order->total_amount,
'get_amount' => $commissionBalance
])) {
DB::rollBack();
return false;
}
$inviteUserId = $inviter->invite_user_id;
}
return true;
}
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Jobs\OrderHandleJob;
use App\Services\OrderService; use App\Services\OrderService;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\Order; use App\Models\Order;
@ -45,20 +46,8 @@ class CheckOrder extends Command
ini_set('memory_limit', -1); ini_set('memory_limit', -1);
$orders = Order::whereIn('status', [0, 1]) $orders = Order::whereIn('status', [0, 1])
->get(); ->get();
foreach ($orders as $item) { foreach ($orders as $order) {
$orderService = new OrderService($item); OrderHandleJob::dispatch($order->trade_no);
switch ($item->status) {
// cancel
case 0:
if (strtotime($item->created_at) <= (time() - 1800)) {
$orderService->cancel();
}
break;
case 1:
$orderService->open();
break;
}
} }
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Plan;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -42,24 +43,45 @@ class ResetTraffic extends Command
*/ */
public function handle() public function handle()
{ {
DB::beginTransaction(); ini_set('memory_limit', -1);
$resetTrafficMethod = config('v2board.reset_traffic_method', 0); foreach (Plan::get() as $plan) {
switch ((int)$resetTrafficMethod) { switch ($plan->reset_traffic_method) {
// 1 a month case null: {
case 0: $resetTrafficMethod = config('v2board.reset_traffic_method', 0);
$this->resetByMonthFirstDay(); switch ((int)$resetTrafficMethod) {
break; // month first day
// expire day case 0:
case 1: $this->resetByMonthFirstDay($this->builder);
$this->resetByExpireDay(); break;
break; // expire day
case 1:
$this->resetByExpireDay($this->builder);
break;
// no action
case 2:
break;
}
break;
}
case 0: {
$builder = $this->builder->where('plan_id', $plan->id);
$this->resetByMonthFirstDay($builder);
break;
}
case 1: {
$builder = $this->builder->where('plan_id', $plan->id);
$this->resetByExpireDay($builder);
break;
}
case 2: {
break;
}
}
} }
DB::commit();
} }
private function resetByMonthFirstDay():void private function resetByMonthFirstDay($builder):void
{ {
$builder = $this->builder;
if ((string)date('d') === '01') { if ((string)date('d') === '01') {
$builder->update([ $builder->update([
'u' => 0, 'u' => 0,
@ -68,9 +90,8 @@ class ResetTraffic extends Command
} }
} }
private function resetByExpireDay():void private function resetByExpireDay($builder):void
{ {
$builder = $this->builder;
$lastDay = date('d', strtotime('last day of +0 months')); $lastDay = date('d', strtotime('last day of +0 months'));
$users = []; $users = [];
foreach ($builder->get() as $item) { foreach ($builder->get() as $item) {

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Services\MailService;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use App\Models\User; use App\Models\User;
use App\Models\MailLog; use App\Models\MailLog;
@ -41,23 +42,9 @@ class SendRemindMail extends Command
public function handle() public function handle()
{ {
$users = User::all(); $users = User::all();
$mailService = new MailService();
foreach ($users as $user) { foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user); if ($user->remind_expire) $mailService->remindExpire($user);
}
}
private function remindExpire($user)
{
if ($user->expired_at !== NULL && ($user->expired_at - 86400) < time() && $user->expired_at > time()) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的服务即将到期',
'template_name' => 'remindExpire',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
} }
} }
} }

View File

@ -3,7 +3,10 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Order; use App\Models\Order;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Matriphe\Larinfo;
class Test extends Command class Test extends Command
{ {

View File

@ -47,13 +47,12 @@ class V2boardInstall extends Command
$this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | "); $this->info(" \ \ / / __) | _ \ / _ \ / _` | '__/ _` | ");
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | "); $this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| "); $this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.lock')) { if (\File::exists(base_path() . '/.env')) {
abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件'); abort(500, 'V2board 已安装,如需重新安装请删除目录下.lock文件');
} }
if (!\File::exists(base_path() . '/.env')) {
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) { if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限'); abort(500, '复制环境文件失败,请检查目录权限');
}
} }
$this->saveToEnv([ $this->saveToEnv([
'APP_KEY' => 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')), 'APP_KEY' => 'base64:' . base64_encode(Encrypter::generateKey('AES-256-CBC')),

View File

@ -42,16 +42,16 @@ class V2boardStatistics extends Command
*/ */
public function handle() public function handle()
{ {
$this->statOrder(); $this->statOrder();
$this->statServer(); $this->statServer();
} }
private function statOrder() private function statOrder()
{ {
$endAt = strtotime(date('Y-m-d')); $endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt); $startAt = strtotime('-1 day', $endAt);
$builder = Order::where('created_at', '>=', $startAt) $builder = Order::where('paid_at', '>=', $startAt)
->where('created_at', '<', $endAt) ->where('paid_at', '<', $endAt)
->whereNotIn('status', [0, 2]); ->whereNotIn('status', [0, 2]);
$orderCount = $builder->count(); $orderCount = $builder->count();
$orderAmount = $builder->sum('total_amount'); $orderAmount = $builder->sum('total_amount');

View File

@ -51,11 +51,12 @@ class V2boardUpdate extends Command
} }
$this->info('正在导入数据库请稍等...'); $this->info('正在导入数据库请稍等...');
foreach ($sql as $item) { foreach ($sql as $item) {
if (!$item) continue;
try { try {
DB::select(DB::raw($item)); DB::select(DB::raw($item));
} catch (\Exception $e) { } catch (\Exception $e) {
} }
} }
$this->info('更新完毕'); $this->info('更新完毕,请重新启动队列服务。');
} }
} }

4
app/Console/Kernel.php Executable file → Normal file
View File

@ -31,9 +31,11 @@ class Kernel extends ConsoleKernel
$schedule->command('check:commission')->everyMinute(); $schedule->command('check:commission')->everyMinute();
// reset // reset
$schedule->command('reset:traffic')->daily(); $schedule->command('reset:traffic')->daily();
$schedule->command('reset:serverLog')->quarterly(); $schedule->command('reset:serverLog')->quarterly()->at('0:15');
// send // send
$schedule->command('send:remindMail')->dailyAt('11:30'); $schedule->command('send:remindMail')->dailyAt('11:30');
// horizon metrics
$schedule->command('horizon:snapshot')->everyFiveMinutes();
} }
/** /**

View File

@ -2,8 +2,8 @@
namespace App\Exceptions; namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@ -29,10 +29,12 @@ class Handler extends ExceptionHandler
/** /**
* Report or log an exception. * Report or log an exception.
* *
* @param \Exception $exception * @param \Throwable $exception
* @return void * @return void
*
* @throws \Throwable
*/ */
public function report(Exception $exception) public function report(Throwable $exception)
{ {
parent::report($exception); parent::report($exception);
} }
@ -40,16 +42,14 @@ class Handler extends ExceptionHandler
/** /**
* Render an exception into an HTTP response. * Render an exception into an HTTP response.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @param \Exception $exception * @param \Throwable $exception
* @return \Illuminate\Http\Response * @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Throwable
*/ */
public function render($request, Exception $exception) public function render($request, Throwable $exception)
{ {
if($exception instanceof \Illuminate\Http\Exceptions\ThrottleRequestsException) {
abort(429, '请求频繁,请稍后再试');
}
return parent::render($request, $exception); return parent::render($request, $exception);
} }
} }

View File

@ -60,7 +60,11 @@ class ConfigController extends Controller
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1), 'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1),
'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100), 'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100),
'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT), 'commission_withdraw_method' => config('v2board.commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0) 'withdraw_close_enable' => config('v2board.withdraw_close_enable', 0),
'commission_distribution_enable' => config('v2board.commission_distribution_enable', 0),
'commission_distribution_l1' => config('v2board.commission_distribution_l1'),
'commission_distribution_l2' => config('v2board.commission_distribution_l2'),
'commission_distribution_l3' => config('v2board.commission_distribution_l3')
], ],
'site' => [ 'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0), 'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
@ -136,9 +140,6 @@ class ConfigController extends Controller
'server_v2ray_domain' => config('v2board.server_v2ray_domain'), 'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'), 'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
], ],
'tutorial' => [
'apple_id' => config('v2board.apple_id')
],
'email' => [ 'email' => [
'email_template' => config('v2board.email_template', 'default'), 'email_template' => config('v2board.email_template', 'default'),
'email_host' => config('v2board.email_host'), 'email_host' => config('v2board.email_host'),
@ -166,7 +167,7 @@ class ConfigController extends Controller
public function save(ConfigSave $request) public function save(ConfigSave $request)
{ {
$data = $request->input(); $data = $request->validated();
$array = \Config::get('v2board'); $array = \Config::get('v2board');
foreach ($data as $k => $v) { foreach ($data as $k => $v) {
if (!in_array($k, array_keys($request->validated()))) { if (!in_array($k, array_keys($request->validated()))) {

View File

@ -24,42 +24,12 @@ class CouponController extends Controller
$total = $builder->count(); $total = $builder->count();
$coupons = $builder->forPage($current, $pageSize) $coupons = $builder->forPage($current, $pageSize)
->get(); ->get();
foreach ($coupons as $k => $v) {
if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']);
}
return response([ return response([
'data' => $coupons, 'data' => $coupons,
'total' => $total 'total' => $total
]); ]);
} }
public function save(CouponSave $request)
{
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) {
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
}
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Coupon::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function generate(CouponGenerate $request) public function generate(CouponGenerate $request)
{ {
if ($request->input('generate_count')) { if ($request->input('generate_count')) {
@ -68,9 +38,6 @@ class CouponController extends Controller
} }
$params = $request->validated(); $params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
if (!$request->input('id')) { if (!$request->input('id')) {
if (!isset($params['code'])) { if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8); $params['code'] = Helper::randomChar(8);
@ -95,10 +62,8 @@ class CouponController extends Controller
{ {
$coupons = []; $coupons = [];
$coupon = $request->validated(); $coupon = $request->validated();
if (isset($coupon['limit_plan_ids'])) {
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
}
$coupon['created_at'] = $coupon['updated_at'] = time(); $coupon['created_at'] = $coupon['updated_at'] = time();
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
unset($coupon['generate_count']); unset($coupon['generate_count']);
for ($i = 0;$i < $request->input('generate_count');$i++) { for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon['code'] = Helper::randomChar(8); $coupon['code'] = Helper::randomChar(8);
@ -118,7 +83,7 @@ class CouponController extends Controller
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']); $endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
$limitUse = $coupon['limit_use'] ?? '不限制'; $limitUse = $coupon['limit_use'] ?? '不限制';
$createTime = date('Y-m-d H:i:s', $coupon['created_at']); $createTime = date('Y-m-d H:i:s', $coupon['created_at']);
$limitPlanIds = $coupon['limit_plan_ids'] ?? '不限制'; $limitPlanIds = implode("/", json_decode($coupon['limit_plan_ids'], true)) ?? '不限制';
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n"; $data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
} }
echo $data; echo $data;

View File

@ -6,6 +6,7 @@ use App\Http\Requests\Admin\OrderAssign;
use App\Http\Requests\Admin\OrderUpdate; use App\Http\Requests\Admin\OrderUpdate;
use App\Http\Requests\Admin\OrderFetch; use App\Http\Requests\Admin\OrderFetch;
use App\Services\OrderService; use App\Services\OrderService;
use App\Services\UserService;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -63,10 +64,45 @@ class OrderController extends Controller
]); ]);
} }
public function paid(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->paid('manual_operation')) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function cancel(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function update(OrderUpdate $request) public function update(OrderUpdate $request)
{ {
$params = $request->only([ $params = $request->only([
'status',
'commission_status' 'commission_status'
]); ]);
@ -76,16 +112,6 @@ class OrderController extends Controller
abort(500, '订单不存在'); abort(500, '订单不存在');
} }
if (isset($params['status']) && (int)$params['status'] === 2) {
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
try { try {
$order->update($params); $order->update($params);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -97,26 +123,6 @@ class OrderController extends Controller
]); ]);
} }
public function repair(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, '参数错误');
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('status', 0)
->first();
if (!$order) {
abort(500, '订单不存在或订单已支付');
}
$order->status = 1;
if (!$order->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function assign(OrderAssign $request) public function assign(OrderAssign $request)
{ {
$plan = Plan::find($request->input('plan_id')); $plan = Plan::find($request->input('plan_id'));
@ -130,6 +136,11 @@ class OrderController extends Controller
abort(500, '该订阅不存在'); abort(500, '该订阅不存在');
} }
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($user->id)) {
abort(500, '该用户还有待支付的订单,无法分配');
}
DB::beginTransaction(); DB::beginTransaction();
$order = new Order(); $order = new Order();
$orderService = new OrderService($order); $orderService = new OrderService($order);

View File

@ -42,6 +42,9 @@ class PaymentController extends Controller
public function save(Request $request) public function save(Request $request)
{ {
if (!config('v2board.app_url')) {
abort(500, '请在站点配置中配置站点地址');
}
if ($request->input('id')) { if ($request->input('id')) {
$payment = Payment::find($request->input('id')); $payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在'); if (!$payment) abort(500, '支付方式不存在');
@ -58,7 +61,7 @@ class PaymentController extends Controller
'name' => $request->input('name'), 'name' => $request->input('name'),
'payment' => $request->input('payment'), 'payment' => $request->input('payment'),
'config' => $request->input('config'), 'config' => $request->input('config'),
'uuid' => Helper::guid() 'uuid' => Helper::randomChar(8)
])) { ])) {
abort(500, '保存失败'); abort(500, '保存失败');
} }

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers\Admin\Server; namespace App\Http\Controllers\Admin\Server;
use App\Models\Plan; use App\Models\Plan;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerGroup; use App\Models\ServerGroup;
use App\Models\User; use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -50,10 +50,9 @@ class GroupController extends Controller
} }
} }
$servers = Server::all(); $servers = ServerV2ray::all();
foreach ($servers as $server) { foreach ($servers as $server) {
$groupId = json_decode($server->group_id); if (in_array($request->input('id'), $server->group_id)) {
if (in_array($request->input('id'), $groupId)) {
abort(500, '该组已被节点所使用,无法删除'); abort(500, '该组已被节点所使用,无法删除');
} }
} }

View File

@ -2,7 +2,7 @@
namespace App\Http\Controllers\Admin\Server; namespace App\Http\Controllers\Admin\Server;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Services\ServerService; use App\Services\ServerService;
@ -32,7 +32,7 @@ class ManageController extends Controller
} }
break; break;
case 'v2ray': case 'v2ray':
if (!Server::find($v['value'])->update(['sort' => $k + 1])) { if (!ServerV2ray::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack(); DB::rollBack();
abort(500, '保存失败'); abort(500, '保存失败');
} }

View File

@ -14,11 +14,6 @@ class ShadowsocksController extends Controller
public function save(ServerShadowsocksSave $request) public function save(ServerShadowsocksSave $request)
{ {
$params = $request->validated(); $params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if ($request->input('id')) { if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id')); $server = ServerShadowsocks::find($request->input('id'));
if (!$server) { if (!$server) {

View File

@ -14,11 +14,6 @@ class TrojanController extends Controller
public function save(ServerTrojanSave $request) public function save(ServerTrojanSave $request)
{ {
$params = $request->validated(); $params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if ($request->input('id')) { if ($request->input('id')) {
$server = ServerTrojan::find($request->input('id')); $server = ServerTrojan::find($request->input('id'));
if (!$server) { if (!$server) {

View File

@ -7,44 +7,16 @@ use App\Http\Requests\Admin\ServerV2rayUpdate;
use App\Services\ServerService; use App\Services\ServerService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Server; use App\Models\ServerV2ray;
class V2rayController extends Controller class V2rayController extends Controller
{ {
public function save(ServerV2raySave $request) public function save(ServerV2raySave $request)
{ {
$params = $request->validated(); $params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
if (isset($params['dnsSettings'])) {
if (!is_object(json_decode($params['dnsSettings']))) {
abort(500, 'DNS规则配置格式不正确');
}
}
if (isset($params['ruleSettings'])) {
if (!is_object(json_decode($params['ruleSettings']))) {
abort(500, '审计规则配置格式不正确');
}
}
if (isset($params['networkSettings'])) {
if (!is_object(json_decode($params['networkSettings']))) {
abort(500, '传输协议配置格式不正确');
}
}
if (isset($params['tlsSettings'])) {
if (!is_object(json_decode($params['tlsSettings']))) {
abort(500, 'TLS配置格式不正确');
}
}
if ($request->input('id')) { if ($request->input('id')) {
$server = Server::find($request->input('id')); $server = ServerV2ray::find($request->input('id'));
if (!$server) { if (!$server) {
abort(500, '服务器不存在'); abort(500, '服务器不存在');
} }
@ -58,7 +30,7 @@ class V2rayController extends Controller
]); ]);
} }
if (!Server::create($params)) { if (!ServerV2ray::create($params)) {
abort(500, '创建失败'); abort(500, '创建失败');
} }
@ -70,7 +42,7 @@ class V2rayController extends Controller
public function drop(Request $request) public function drop(Request $request)
{ {
if ($request->input('id')) { if ($request->input('id')) {
$server = Server::find($request->input('id')); $server = ServerV2ray::find($request->input('id'));
if (!$server) { if (!$server) {
abort(500, '节点ID不存在'); abort(500, '节点ID不存在');
} }
@ -86,7 +58,7 @@ class V2rayController extends Controller
'show', 'show',
]); ]);
$server = Server::find($request->input('id')); $server = ServerV2ray::find($request->input('id'));
if (!$server) { if (!$server) {
abort(500, '该服务器不存在'); abort(500, '该服务器不存在');
@ -104,12 +76,12 @@ class V2rayController extends Controller
public function copy(Request $request) public function copy(Request $request)
{ {
$server = Server::find($request->input('id')); $server = ServerV2ray::find($request->input('id'));
$server->show = 0; $server->show = 0;
if (!$server) { if (!$server) {
abort(500, '服务器不存在'); abort(500, '服务器不存在');
} }
if (!Server::create($server->toArray())) { if (!ServerV2ray::create($server->toArray())) {
abort(500, '复制失败'); abort(500, '复制失败');
} }

View File

@ -8,7 +8,7 @@ use App\Services\ServerService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ServerGroup; use App\Models\ServerGroup;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\Plan; use App\Models\Plan;
use App\Models\User; use App\Models\User;
use App\Models\Ticket; use App\Models\Ticket;
@ -91,7 +91,7 @@ class StatController extends Controller
{ {
$servers = [ $servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(), 'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'vmess' => Server::where('parent_id', null)->get()->toArray(), 'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray() 'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
]; ];
$timestamp = strtotime('-1 day', strtotime(date('Y-m-d'))); $timestamp = strtotime('-1 day', strtotime(date('Y-m-d')));

View File

@ -68,7 +68,7 @@ class UserController extends Controller
$res[$i]['plan_name'] = $plan[$k]['name']; $res[$i]['plan_name'] = $plan[$k]['name'];
} }
} }
$res[$i]['subscribe_url'] = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $res[$i]['token']; $res[$i]['subscribe_url'] = Helper::getSubscribeHost() . '/api/v1/client/subscribe?token=' . $res[$i]['token'];
} }
return response([ return response([
'data' => $res, 'data' => $res,

View File

@ -7,7 +7,7 @@ use App\Services\ServerService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\Clash; use App\Utils\Clash;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Server; use App\Models\ServerV2ray;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;

View File

@ -4,7 +4,7 @@ namespace App\Http\Controllers\Client\Protocols;
class AnXray class AnXray
{ {
public $flag = 'anxray'; public $flag = 'axxray';
private $servers; private $servers;
private $user; private $user;
@ -64,15 +64,21 @@ class AnXray
"encryption" => "none", "encryption" => "none",
"type" => urlencode($server['network']), "type" => urlencode($server['network']),
"security" => $server['tls'] ? "tls" : "", "security" => $server['tls'] ? "tls" : "",
"sni" => $server['tls'] ? urlencode(json_decode($server['tlsSettings'], true)['serverName']) : ""
]; ];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = urlencode($tlsSettings['serverName']);
}
}
if ((string)$server['network'] === 'ws') { if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']); if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']);
if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']); if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']);
} }
if ((string)$server['network'] === 'grpc') { if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['serviceName'] = urlencode($grpcSettings['serviceName']); if (isset($grpcSettings['serviceName'])) $config['serviceName'] = urlencode($grpcSettings['serviceName']);
} }
return "vmess://" . $uuid . "@" . $server['host'] . ":" . $server['port'] . "?" . http_build_query($config) . "#" . urlencode($server['name']) . "\r\n"; return "vmess://" . $uuid . "@" . $server['host'] . ":" . $server['port'] . "?" . http_build_query($config) . "#" . urlencode($server['name']) . "\r\n";

View File

@ -20,7 +20,10 @@ class Clash
{ {
$servers = $this->servers; $servers = $this->servers;
$user = $this->user; $user = $this->user;
$appName = config('v2board.app_name', 'V2Board');
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}"); header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
header('profile-update-interval: 24');
header("content-disposition: filename={$appName}");
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml'; $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml'; $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
if (\File::exists($customConfig)) { if (\File::exists($customConfig)) {
@ -51,6 +54,11 @@ class Clash
if (!is_array($config['proxy-groups'][$k]['proxies'])) continue; if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
} }
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['SERVER_NAME'];
$subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], $subsDomainRule);
$yaml = Yaml::dump($config); $yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml); $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml; return $yaml;
@ -84,7 +92,7 @@ class Clash
if ($server['tls']) { if ($server['tls']) {
$array['tls'] = true; $array['tls'] = true;
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false); $array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -94,7 +102,7 @@ class Clash
if ($server['network'] === 'ws') { if ($server['network'] === 'ws') {
$array['network'] = 'ws'; $array['network'] = 'ws';
if ($server['networkSettings']) { if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path'])) if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$array['ws-path'] = $wsSettings['path']; $array['ws-path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host'])) if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
@ -104,7 +112,7 @@ class Clash
if ($server['network'] === 'grpc') { if ($server['network'] === 'grpc') {
$array['network'] = 'grpc'; $array['network'] = 'grpc';
if ($server['networkSettings']) { if ($server['networkSettings']) {
$grpcObject = json_decode($server['networkSettings'], true); $grpcObject = $server['networkSettings'];
$array['grpc-opts'] = []; $array['grpc-opts'] = [];
$array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName']; $array['grpc-opts']['grpc-service-name'] = $grpcObject['serviceName'];
} }

View File

@ -63,19 +63,19 @@ class Passwall
]; ];
if ($server['tls']) { if ($server['tls']) {
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName']; $config['sni'] = $tlsSettings['serverName'];
} }
} }
if ((string)$server['network'] === 'ws') { if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host']; if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
} }
if ((string)$server['network'] === 'grpc') { if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName']; if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
} }
return "vmess://" . base64_encode(json_encode($config)) . "\r\n"; return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
} }

View File

@ -66,7 +66,7 @@ class QuantumultX
if ($server['network'] === 'tcp') if ($server['network'] === 'tcp')
array_push($config, 'obfs=over-tls'); array_push($config, 'obfs=over-tls');
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'tls-verification=' . ($tlsSettings['allowInsecure'] ? 'false' : 'true')); array_push($config, 'tls-verification=' . ($tlsSettings['allowInsecure'] ? 'false' : 'true'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -79,7 +79,7 @@ class QuantumultX
else else
array_push($config, 'obfs=ws'); array_push($config, 'obfs=ws');
if ($server['networkSettings']) { if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path'])) if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "obfs-uri={$wsSettings['path']}"); array_push($config, "obfs-uri={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']) && !isset($host)) if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']) && !isset($host))

View File

@ -63,19 +63,19 @@ class SSRPlus
]; ];
if ($server['tls']) { if ($server['tls']) {
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName']; $config['sni'] = $tlsSettings['serverName'];
} }
} }
if ((string)$server['network'] === 'ws') { if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host']; if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
} }
if ((string)$server['network'] === 'grpc') { if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName']; if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
} }
return "vmess://" . base64_encode(json_encode($config)) . "\r\n"; return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
} }

View File

@ -63,7 +63,7 @@ class Shadowrocket
if ($server['tls']) { if ($server['tls']) {
$config['tls'] = 1; $config['tls'] = 1;
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$config['allowInsecure'] = (int)$tlsSettings['allowInsecure']; $config['allowInsecure'] = (int)$tlsSettings['allowInsecure'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -73,7 +73,7 @@ class Shadowrocket
if ($server['network'] === 'ws') { if ($server['network'] === 'ws') {
$config['obfs'] = "websocket"; $config['obfs'] = "websocket";
if ($server['networkSettings']) { if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path'])) if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$config['path'] = $wsSettings['path']; $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host'])) if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
@ -83,7 +83,7 @@ class Shadowrocket
if ($server['network'] === 'grpc') { if ($server['network'] === 'grpc') {
$config['obfs'] = "grpc"; $config['obfs'] = "grpc";
if ($server['networkSettings']) { if ($server['networkSettings']) {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName']) && !empty($grpcSettings['serviceName'])) if (isset($grpcSettings['serviceName']) && !empty($grpcSettings['serviceName']))
$config['path'] = $grpcSettings['serviceName']; $config['path'] = $grpcSettings['serviceName'];
} }

View File

@ -26,13 +26,19 @@ class Surfboard
foreach ($servers as $item) { foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') { if ($item['type'] === 'shadowsocks') {
// [Proxy] // [Proxy]
$proxies .= Surfboard::buildShadowsocks($user['uuid'], $item); $proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group] // [Proxy Group]
$proxyGroup .= $item['name'] . ', '; $proxyGroup .= $item['name'] . ', ';
} }
if ($item['type'] === 'v2ray') { if ($item['type'] === 'v2ray') {
// [Proxy] // [Proxy]
$proxies .= Surfboard::buildVmess($user['uuid'], $item); $proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'trojan') {
// [Proxy]
$proxies .= self::buildTrojan($user['uuid'], $item);
// [Proxy Group] // [Proxy Group]
$proxyGroup .= $item['name'] . ', '; $proxyGroup .= $item['name'] . ', ';
} }
@ -48,8 +54,10 @@ class Surfboard
// Subscription link // Subscription link
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']; $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$subsDomain = $_SERVER['SERVER_NAME'];
$config = str_replace('$subs_link', $subsURL, $config); $config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config); $config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config); $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config; return $config;
@ -88,7 +96,7 @@ class Surfboard
if ($server['tls']) { if ($server['tls']) {
array_push($config, 'tls=true'); array_push($config, 'tls=true');
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false')); array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -98,7 +106,7 @@ class Surfboard
if ($server['network'] === 'ws') { if ($server['network'] === 'ws') {
array_push($config, 'ws=true'); array_push($config, 'ws=true');
if ($server['networkSettings']) { if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path'])) if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}"); array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host'])) if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
@ -110,4 +118,24 @@ class Surfboard
$uri .= "\r\n"; $uri .= "\r\n";
return $uri; return $uri;
} }
public static function buildTrojan($password, $server)
{
$config = [
"{$server['name']}=trojan",
"{$server['host']}",
"{$server['port']}",
"password={$password}",
$server['server_name'] ? "sni={$server['server_name']}" : "",
'tfo=true',
'udp-relay=true'
];
if (!empty($server['allow_insecure'])) {
array_push($config, $server['allow_insecure'] ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
} }

View File

@ -53,8 +53,10 @@ class Surge
// Subscription link // Subscription link
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']; $subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$subsDomain = $_SERVER['SERVER_NAME'];
$config = str_replace('$subs_link', $subsURL, $config); $config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config); $config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config); $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
return $config; return $config;
@ -92,7 +94,7 @@ class Surge
if ($server['tls']) { if ($server['tls']) {
array_push($config, 'tls=true'); array_push($config, 'tls=true');
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure'])) if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false')); array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
@ -102,7 +104,7 @@ class Surge
if ($server['network'] === 'ws') { if ($server['network'] === 'ws') {
array_push($config, 'ws=true'); array_push($config, 'ws=true');
if ($server['networkSettings']) { if ($server['networkSettings']) {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path'])) if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "ws-path={$wsSettings['path']}"); array_push($config, "ws-path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host'])) if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))

View File

@ -63,19 +63,19 @@ class V2rayN
]; ];
if ($server['tls']) { if ($server['tls']) {
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName']; $config['sni'] = $tlsSettings['serverName'];
} }
} }
if ((string)$server['network'] === 'ws') { if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host']; if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
} }
if ((string)$server['network'] === 'grpc') { if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName']; if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
} }
return "vmess://" . base64_encode(json_encode($config)) . "\r\n"; return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
} }

View File

@ -63,19 +63,19 @@ class V2rayNG
]; ];
if ($server['tls']) { if ($server['tls']) {
if ($server['tlsSettings']) { if ($server['tlsSettings']) {
$tlsSettings = json_decode($server['tlsSettings'], true); $tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName'])) if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName']; $config['sni'] = $tlsSettings['serverName'];
} }
} }
if ((string)$server['network'] === 'ws') { if ((string)$server['network'] === 'ws') {
$wsSettings = json_decode($server['networkSettings'], true); $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host']; if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
} }
if ((string)$server['network'] === 'grpc') { if ((string)$server['network'] === 'grpc') {
$grpcSettings = json_decode($server['networkSettings'], true); $grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['path'])) $config['path'] = $grpcSettings['serviceName']; if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
} }
return "vmess://" . base64_encode(json_encode($config)) . "\r\n"; return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
} }

View File

@ -34,7 +34,7 @@ class PaymentController extends Controller
} }
if ($order->status === 1) return true; if ($order->status === 1) return true;
$orderService = new OrderService($order); $orderService = new OrderService($order);
if (!$orderService->success($callbackNo)) { if (!$orderService->paid($callbackNo)) {
return false; return false;
} }
$telegramService = new TelegramService(); $telegramService = new TelegramService();

View File

@ -102,10 +102,15 @@ class AuthController extends Controller
if ((int)config('v2board.email_verify', 0)) { if ((int)config('v2board.email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))); Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
} }
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$request->session()->put('email', $user->email); $request->session()->put('email', $user->email);
$request->session()->put('id', $user->id); $request->session()->put('id', $user->id);
return response()->json([ return response()->json([
'data' => true 'data' => $data
]); ]);
} }
@ -120,6 +125,7 @@ class AuthController extends Controller
} }
if (!Helper::multiPasswordVerify( if (!Helper::multiPasswordVerify(
$user->password_algo, $user->password_algo,
$user->password_salt,
$password, $password,
$user->password) $user->password)
) { ) {
@ -250,6 +256,7 @@ class AuthController extends Controller
} }
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT); $user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL; $user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) { if (!$user->save()) {
abort(500, __('Reset failed')); abort(500, __('Reset failed'));
} }

View File

@ -8,7 +8,7 @@ use App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerLog; use App\Models\ServerLog;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -35,13 +35,13 @@ class DeepbworkController extends Controller
public function user(Request $request) public function user(Request $request)
{ {
$nodeId = $request->input('node_id'); $nodeId = $request->input('node_id');
$server = Server::find($nodeId); $server = ServerV2ray::find($nodeId);
if (!$server) { if (!$server) {
abort(500, 'fail'); abort(500, 'fail');
} }
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService(); $serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id)); $users = $serverService->getAvailableUsers($server->group_id);
$result = []; $result = [];
foreach ($users as $user) { foreach ($users as $user) {
$user->v2ray_user = [ $user->v2ray_user = [
@ -64,7 +64,7 @@ class DeepbworkController extends Controller
public function submit(Request $request) public function submit(Request $request)
{ {
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input')); // Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = Server::find($request->input('node_id')); $server = ServerV2ray::find($request->input('node_id'));
if (!$server) { if (!$server) {
return response([ return response([
'ret' => 0, 'ret' => 0,
@ -76,23 +76,11 @@ class DeepbworkController extends Controller
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600); Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
DB::beginTransaction(); foreach ($data as $item) {
try { $u = $item['u'] * $server->rate;
foreach ($data as $item) { $d = $item['d'] * $server->rate;
$u = $item['u'] * $server->rate; $userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess');
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
} }
DB::commit();
return response([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -1,158 +0,0 @@
<?php
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
use App\Models\Server;
use App\Models\ServerLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
/*
* V2ray Poseidon
* Github: https://github.com/ColetteContreras/trojan-poseidon
*/
class PoseidonController extends Controller
{
public $poseidonVersion;
public function __construct(Request $request)
{
$this->poseidonVersion = $request->input('poseidon_version');
}
// 后端获取用户
public function user(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$server = Server::find($nodeId);
if (!$server) {
return $this->error("server could not be found", 404);
}
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id));
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [
"uuid" => $user->uuid,
"email" => sprintf("%s@v2board.user", $user->uuid),
"alter_id" => $server->alter_id,
"level" => 0,
];
unset($user['uuid']);
unset($user['email']);
array_push($result, $user);
}
return $this->success($result);
}
// 后端提交数据
public function submit(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$server = Server::find($request->input('node_id'));
if (!$server) {
return $this->error("server could not be found", 404);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_V2RAY_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'vmess')) {
return $this->error("user fetch fail", 500);
}
}
return $this->success('');
}
// 后端获取配置
public function config(Request $request)
{
if ($r = $this->verifyToken($request)) { return $r; }
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
return $this->error('invalid parameters', 400);
}
$serverService = new ServerService();
try {
$json = $serverService->getV2RayConfig($nodeId, $localPort);
$json->poseidon = [
'license_key' => (string)config('v2board.server_license'),
];
if ($this->poseidonVersion >= 'v1.5.0') {
// don't need it after v1.5.0
unset($json->inboundDetour);
unset($json->stats);
unset($json->api);
array_shift($json->routing->rules);
}
foreach($json->policy->levels as &$level) {
$level->handshake = 2;
$level->uplinkOnly = 2;
$level->downlinkOnly = 2;
$level->connIdle = 60;
}
return $this->success($json);
} catch (\Exception $e) {
return $this->error($e->getMessage(), 500);
}
}
protected function verifyToken(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
return $this->error("token must be set");
}
if ($token !== config('v2board.server_token')) {
return $this->error("invalid token");
}
}
protected function error($msg, int $status = 400) {
return response([
'msg' => $msg,
], $status);
}
protected function success($data) {
$req = request();
// Only for "GET" method
if (!$req->isMethod('GET') || !$data) {
return response([
'msg' => 'ok',
'data' => $data,
]);
}
$etag = sha1(json_encode($data));
if ($etag == $req->header("IF-NONE-MATCH")) {
return response(null, 304);
}
return response([
'msg' => 'ok',
'data' => $data,
])->header('ETAG', $etag);
}
}

View File

@ -8,10 +8,6 @@ use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
/* /*
@ -41,7 +37,7 @@ class ShadowsocksTidalabController extends Controller
} }
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService(); $serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id)); $users = $serverService->getAvailableUsers($server->group_id);
$result = []; $result = [];
foreach ($users as $user) { foreach ($users as $user) {
array_push($result, [ array_push($result, [
@ -72,23 +68,11 @@ class ShadowsocksTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600); Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
DB::beginTransaction(); foreach ($data as $item) {
try { $u = $item['u'] * $server->rate;
foreach ($data as $item) { $d = $item['d'] * $server->rate;
$u = $item['u'] * $server->rate; $userService->trafficFetch($u, $d, $item['user_id'], $server, 'shadowsocks');
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'], $server, 'shadowsocks')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
} }
DB::commit();
return response([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -41,7 +41,7 @@ class TrojanTidalabController extends Controller
} }
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService(); $serverService = new ServerService();
$users = $serverService->getAvailableUsers(json_decode($server->group_id)); $users = $serverService->getAvailableUsers($server->group_id);
$result = []; $result = [];
foreach ($users as $user) { foreach ($users as $user) {
$user->trojan_user = [ $user->trojan_user = [
@ -73,23 +73,11 @@ class TrojanTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600); Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
DB::beginTransaction(); foreach ($data as $item) {
try { $u = $item['u'] * $server->rate;
foreach ($data as $item) { $d = $item['d'] * $server->rate;
$u = $item['u'] * $server->rate; $userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan');
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'], $server, 'trojan')) {
continue;
}
}
} catch (\Exception $e) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
} }
DB::commit();
return response([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -27,9 +27,8 @@ class CommController extends Controller
->where('payment', 'StripeCredit') ->where('payment', 'StripeCredit')
->first(); ->first();
if (!$payment) abort(500, 'payment is not found'); if (!$payment) abort(500, 'payment is not found');
$config = json_decode($payment->config, true);
return response([ return response([
'data' => $config['stripe_pk_live'] 'data' => $payment->config['stripe_pk_live']
]); ]);
} }
} }

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\User; namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\CouponService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Coupon; use App\Models\Coupon;
@ -13,27 +14,12 @@ class CouponController extends Controller
if (empty($request->input('code'))) { if (empty($request->input('code'))) {
abort(500, __('Coupon cannot be empty')); abort(500, __('Coupon cannot be empty'));
} }
$coupon = Coupon::where('code', $request->input('code'))->first(); $couponService = new CouponService($request->input('code'));
if (!$coupon) { $couponService->setPlanId($request->input('plan_id'));
abort(500, __('Invalid coupon')); $couponService->setUserId($request->session()->get('id'));
} $couponService->check();
if ($coupon->limit_use <= 0 && $coupon->limit_use !== NULL) {
abort(500, __('This coupon is no longer available'));
}
if (time() < $coupon->started_at) {
abort(500, __('This coupon has not yet started'));
}
if (time() > $coupon->ended_at) {
abort(500, __('This coupon has expired'));
}
if ($coupon->limit_plan_ids) {
$limitPlanIds = json_decode($coupon->limit_plan_ids);
if (!in_array($request->input('plan_id'), $limitPlanIds)) {
abort(500, __('The coupon code cannot be used for this subscription'));
}
}
return response([ return response([
'data' => $coupon 'data' => $couponService->getCoupon()
]); ]);
} }
} }

View File

@ -35,8 +35,6 @@ class KnowledgeController extends Controller
} }
$subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}"; $subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
$knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']); $knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
$knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
$knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $knowledge['body']);
$knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']); $knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']);
$knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']); $knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']);
$knowledge['body'] = str_replace( $knowledge['body'] = str_replace(

View File

@ -108,7 +108,7 @@ class OrderController extends Controller
$order->user_id = $request->session()->get('id'); $order->user_id = $request->session()->get('id');
$order->plan_id = $plan->id; $order->plan_id = $plan->id;
$order->cycle = $request->input('cycle'); $order->cycle = $request->input('cycle');
$order->trade_no = Helper::guid(); $order->trade_no = Helper::generateOrderNo();
$order->total_amount = $plan[$request->input('cycle')]; $order->total_amount = $plan[$request->input('cycle')];
if ($request->input('coupon_code')) { if ($request->input('coupon_code')) {
@ -169,9 +169,8 @@ class OrderController extends Controller
} }
// free process // free process
if ($order->total_amount <= 0) { if ($order->total_amount <= 0) {
$order->total_amount = 0; $orderService = new OrderService($order);
$order->status = 1; if (!$orderService->paid($order->trade_no)) abort(500, '');
$order->save();
return response([ return response([
'type' => -1, 'type' => -1,
'data' => true 'data' => true

View File

@ -8,7 +8,7 @@ use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Models\User; use App\Models\User;
@ -36,16 +36,16 @@ class ServerController extends Controller
$current = $request->input('current') ? $request->input('current') : 1; $current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$serverLogModel = ServerLog::where('user_id', $request->session()->get('id')) $serverLogModel = ServerLog::where('user_id', $request->session()->get('id'))
->orderBy('created_at', 'DESC'); ->orderBy('log_at', 'DESC');
switch ($type) { switch ($type) {
case 0: case 0:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d'))); $serverLogModel->where('log_at', '>=', strtotime(date('Y-m-d')));
break; break;
case 1: case 1:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-d')) - 604800); $serverLogModel->where('log_at', '>=', strtotime(date('Y-m-d')) - 604800);
break; break;
case 2: case 2:
$serverLogModel->where('created_at', '>=', strtotime(date('Y-m-1'))); $serverLogModel->where('log_at', '>=', strtotime(date('Y-m-1')));
} }
$total = $serverLogModel->count(); $total = $serverLogModel->count();
$res = $serverLogModel->forPage($current, $pageSize) $res = $serverLogModel->forPage($current, $pageSize)

View File

@ -168,7 +168,7 @@ class TicketController extends Controller
$user = User::find($request->session()->get('id')); $user = User::find($request->session()->get('id'));
$limit = config('v2board.commission_withdraw_limit', 100); $limit = config('v2board.commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) { if ($limit > ($user->commission_balance / 100)) {
abort(500, __('The current required minimum withdrawal commission is', ['limit' => $limit])); abort(500, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit]));
} }
DB::beginTransaction(); DB::beginTransaction();
$subject = __('[Commission Withdrawal Request] This ticket is opened by the system'); $subject = __('[Commission Withdrawal Request] This ticket is opened by the system');

View File

@ -6,14 +6,16 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserTransfer; use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate; use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword; use App\Http\Requests\User\UserChangePassword;
use App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\Ticket; use App\Models\Ticket;
use App\Utils\Helper; use App\Utils\Helper;
use App\Models\Order; use App\Models\Order;
use App\Models\ServerLog; use App\Models\ServerLog;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller class UserController extends Controller
{ {
@ -33,6 +35,7 @@ class UserController extends Controller
} }
if (!Helper::multiPasswordVerify( if (!Helper::multiPasswordVerify(
$user->password_algo, $user->password_algo,
$user->password_salt,
$request->input('old_password'), $request->input('old_password'),
$user->password) $user->password)
) { ) {
@ -40,6 +43,7 @@ class UserController extends Controller
} }
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT); $user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL; $user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) { if (!$user->save()) {
abort(500, __('Save failed')); abort(500, __('Save failed'));
} }
@ -118,12 +122,7 @@ class UserController extends Controller
abort(500, __('Subscription plan does not exist')); abort(500, __('Subscription plan does not exist'));
} }
} }
$subscribeUrl = config('v2board.app_url', env('APP_URL')); $user['subscribe_url'] = Helper::getSubscribeHost() . "/api/v1/client/subscribe?token={$user['token']}";
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
if ($subscribeUrls) {
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
}
$user['subscribe_url'] = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
$user['reset_day'] = $this->getResetDay($user); $user['reset_day'] = $this->getResetDay($user);
return response([ return response([
'data' => $user 'data' => $user
@ -209,4 +208,26 @@ class UserController extends Controller
} }
return null; return null;
} }
public function getQuickLoginUrl(Request $request)
{
$user = User::find($request->session()->get('id'));
if (!$user) {
abort(500, __('The user does not exist'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
} }

View File

@ -35,6 +35,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class, \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CORS::class,
], ],
'api' => [ 'api' => [

View File

@ -15,8 +15,9 @@ class User
*/ */
public function handle($request, Closure $next) public function handle($request, Closure $next)
{ {
if ($request->input('auth_data')) { $authorization = $request->input('auth_data') ?? $request->header('authorization');
$authData = explode(':', base64_decode($request->input('auth_data'))); if ($authorization) {
$authData = explode(':', base64_decode($authorization));
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入'); if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1]) $user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0]) ->where('email', $authData[0])

View File

@ -25,6 +25,10 @@ class ConfigSave extends FormRequest
'commission_withdraw_limit' => 'nullable|numeric', 'commission_withdraw_limit' => 'nullable|numeric',
'commission_withdraw_method' => 'nullable|array', 'commission_withdraw_method' => 'nullable|array',
'withdraw_close_enable' => 'in:0,1', 'withdraw_close_enable' => 'in:0,1',
'commission_distribution_enable' => 'in:0,1',
'commission_distribution_l1' => 'nullable|numeric',
'commission_distribution_l2' => 'nullable|numeric',
'commission_distribution_l3' => 'nullable|numeric',
// site // site
'stop_register' => 'in:0,1', 'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1', 'email_verify' => 'in:0,1',
@ -44,7 +48,7 @@ class ConfigSave extends FormRequest
'tos_url' => 'nullable|url', 'tos_url' => 'nullable|url',
// subscribe // subscribe
'plan_change_enable' => 'in:0,1', 'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1', 'reset_traffic_method' => 'in:0,1,2',
'surplus_enable' => 'in:0,1', 'surplus_enable' => 'in:0,1',
'new_order_event_id' => 'in:0,1', 'new_order_event_id' => 'in:0,1',
'renew_order_event_id' => 'in:0,1', 'renew_order_event_id' => 'in:0,1',
@ -88,14 +92,11 @@ class ConfigSave extends FormRequest
'frontend_theme' => '', 'frontend_theme' => '',
'frontend_theme_sidebar' => 'in:dark,light', 'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light', 'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black', 'frontend_theme_color' => 'in:default,darkblue,black,green',
'frontend_background_url' => 'nullable|url', 'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '', 'frontend_admin_path' => '',
'frontend_customer_service_method' => '', 'frontend_customer_service_method' => '',
'frontend_customer_service_id' => '', 'frontend_customer_service_id' => '',
// tutorial
'apple_id' => 'nullable|email',
'apple_id_password' => '',
// email // email
'email_template' => '', 'email_template' => '',
'email_host' => '', 'email_host' => '',

View File

@ -21,6 +21,7 @@ class CouponGenerate extends FormRequest
'started_at' => 'required|integer', 'started_at' => 'required|integer',
'ended_at' => 'required|integer', 'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer', 'limit_use' => 'nullable|integer',
'limit_use_with_user' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array', 'limit_plan_ids' => 'nullable|array',
'code' => '' 'code' => ''
]; ];
@ -40,7 +41,8 @@ class CouponGenerate extends FormRequest
'started_at.integer' => '开始时间格式有误', 'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空', 'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误', 'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误', 'limit_use.integer' => '最大使用次数格式有误',
'limit_use_with_user.integer' => '限制用户使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误' 'limit_plan_ids.array' => '指定订阅格式有误'
]; ];
} }

View File

@ -1,44 +0,0 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class CouponSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'type' => 'required|in:1,2',
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array',
'code' => ''
];
}
public function messages()
{
return [
'name.required' => '名称不能为空',
'type.required' => '类型不能为空',
'type.in' => '类型格式有误',
'value.required' => '金额或比例不能为空',
'value.integer' => '金额或比例格式有误',
'started_at.required' => '开始时间不能为空',
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误'
];
}
}

View File

@ -25,7 +25,8 @@ class PlanSave extends FormRequest
'two_year_price' => 'nullable|integer', 'two_year_price' => 'nullable|integer',
'three_year_price' => 'nullable|integer', 'three_year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer', 'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer' 'reset_price' => 'nullable|integer',
'reset_traffic_method' => 'nullable|integer|in:0,1,2'
]; ];
} }
@ -44,7 +45,9 @@ class PlanSave extends FormRequest
'two_year_price.integer' => '两年付金额格式有误', 'two_year_price.integer' => '两年付金额格式有误',
'three_year_price.integer' => '三年付金额格式有误', 'three_year_price.integer' => '三年付金额格式有误',
'onetime_price.integer' => '一次性金额有误', 'onetime_price.integer' => '一次性金额有误',
'reset_price.integer' => '流量重置包金额有误' 'reset_price.integer' => '流量重置包金额有误',
'reset_traffic_method.integer' => '流量重置方式格式有误',
'reset_traffic_method.in' => '流量重置方式格式有误'
]; ];
} }
} }

View File

@ -26,10 +26,10 @@ class ServerV2raySave extends FormRequest
'rate' => 'required|numeric', 'rate' => 'required|numeric',
'alter_id' => 'required|integer', 'alter_id' => 'required|integer',
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc', 'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
'networkSettings' => '', 'networkSettings' => 'nullable|array',
'ruleSettings' => '', 'ruleSettings' => 'nullable|array',
'tlsSettings' => '', 'tlsSettings' => 'nullable|array',
'dnsSettings' => '' 'dnsSettings' => 'nullable|array'
]; ];
} }
@ -48,7 +48,11 @@ class ServerV2raySave extends FormRequest
'rate.required' => '倍率不能为空', 'rate.required' => '倍率不能为空',
'rate.numeric' => '倍率格式不正确', 'rate.numeric' => '倍率格式不正确',
'network.required' => '传输协议不能为空', 'network.required' => '传输协议不能为空',
'network.in' => '传输协议格式不正确' 'network.in' => '传输协议格式不正确',
'networkSettings.array' => '传输协议配置有误',
'ruleSettings.array' => '规则配置有误',
'tlsSettings.array' => 'tls配置有误',
'dnsSettings.array' => 'dns配置有误'
]; ];
} }
} }

View File

@ -63,9 +63,10 @@ class AdminRoute
}); });
// Order // Order
$router->get ('/order/fetch', 'Admin\\OrderController@fetch'); $router->get ('/order/fetch', 'Admin\\OrderController@fetch');
$router->post('/order/repair', 'Admin\\OrderController@repair');
$router->post('/order/update', 'Admin\\OrderController@update'); $router->post('/order/update', 'Admin\\OrderController@update');
$router->post('/order/assign', 'Admin\\OrderController@assign'); $router->post('/order/assign', 'Admin\\OrderController@assign');
$router->post('/order/paid', 'Admin\\OrderController@paid');
$router->post('/order/cancel', 'Admin\\OrderController@cancel');
// User // User
$router->get ('/user/fetch', 'Admin\\UserController@fetch'); $router->get ('/user/fetch', 'Admin\\UserController@fetch');
$router->post('/user/update', 'Admin\\UserController@update'); $router->post('/user/update', 'Admin\\UserController@update');

View File

@ -20,6 +20,7 @@ class UserRoute
$router->get ('/getSubscribe', 'User\\UserController@getSubscribe'); $router->get ('/getSubscribe', 'User\\UserController@getSubscribe');
$router->get ('/getStat', 'User\\UserController@getStat'); $router->get ('/getStat', 'User\\UserController@getStat');
$router->post('/transfer', 'User\\UserController@transfer'); $router->post('/transfer', 'User\\UserController@transfer');
$router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
// Order // Order
$router->post('/order/save', 'User\\OrderController@save'); $router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout'); $router->post('/order/checkout', 'User\\OrderController@checkout');

View File

@ -0,0 +1,52 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use App\Services\OrderService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class OrderHandleJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($tradeNo)
{
$this->onQueue('order_handle');
$this->order = Order::where('trade_no', $tradeNo)
->lockForUpdate()
->first();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (!$this->order) return;
$orderService = new OrderService($this->order);
switch ($this->order->status) {
// cancel
case 0:
if ($this->order->created_at <= (time() - 1800)) {
$orderService->cancel();
}
break;
case 1:
$orderService->open();
break;
}
}
}

58
app/Jobs/ServerLogJob.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Jobs;
use App\Services\ServerService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerLogJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $userId;
protected $server;
protected $protocol;
public $tries = 3;
public $timeout = 3;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $userId, $server, $protocol)
{
$this->onQueue('server_log');
$this->u = $u;
$this->d = $d;
$this->userId = $userId;
$this->server = $server;
$this->protocol = $protocol;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$serverService = new ServerService();
if (!$serverService->log(
$this->userId,
$this->server->id,
$this->u,
$this->d,
$this->server->rate,
$this->protocol
)) {
throw new \Exception('日志记录失败');
}
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace App\Jobs;
use App\Models\User;
use App\Services\MailService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TrafficFetchJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $userId;
protected $server;
protected $protocol;
public $tries = 3;
public $timeout = 3;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $userId, $server, $protocol)
{
$this->onQueue('traffic_fetch');
$this->u = $u;
$this->d = $d;
$this->userId = $userId;
$this->server = $server;
$this->protocol = $protocol;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$user = User::lockForUpdate()->find($this->userId);
if (!$user) return;
$user->t = time();
$user->u = $user->u + $this->u;
$user->d = $user->d + $this->d;
if (!$user->save()) throw new \Exception('流量更新失败');
$mailService = new MailService();
$mailService->remindTraffic($user);
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class CommissionLog extends Model
{
protected $table = 'v2_commission_log';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
}

View File

@ -9,4 +9,9 @@ class Coupon extends Model
protected $table = 'v2_coupon'; protected $table = 'v2_coupon';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'limit_plan_ids' => 'array'
];
} }

View File

@ -8,4 +8,8 @@ class InviteCode extends Model
{ {
protected $table = 'v2_invite_code'; protected $table = 'v2_invite_code';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class Knowledge extends Model
protected $table = 'v2_knowledge'; protected $table = 'v2_knowledge';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class MailLog extends Model
protected $table = 'v2_mail_log'; protected $table = 'v2_mail_log';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class Notice extends Model
protected $table = 'v2_notice'; protected $table = 'v2_notice';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,9 @@ class Order extends Model
protected $table = 'v2_order'; protected $table = 'v2_order';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'surplus_order_ids' => 'array'
];
} }

View File

@ -9,4 +9,9 @@ class Payment extends Model
protected $table = 'v2_payment'; protected $table = 'v2_payment';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'config' => 'array'
];
} }

View File

@ -9,4 +9,8 @@ class Plan extends Model
protected $table = 'v2_plan'; protected $table = 'v2_plan';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -1,12 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Server extends Model
{
protected $table = 'v2_server';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

View File

@ -8,4 +8,8 @@ class ServerGroup extends Model
{ {
protected $table = 'v2_server_group'; protected $table = 'v2_server_group';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class ServerLog extends Model
{ {
protected $table = 'v2_server_log'; protected $table = 'v2_server_log';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,10 @@ class ServerShadowsocks extends Model
protected $table = 'v2_server_shadowsocks'; protected $table = 'v2_server_shadowsocks';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tags' => 'array'
];
} }

View File

@ -9,4 +9,8 @@ class ServerStat extends Model
protected $table = 'v2_server_stat'; protected $table = 'v2_server_stat';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,10 @@ class ServerTrojan extends Model
protected $table = 'v2_server_trojan'; protected $table = 'v2_server_trojan';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tags' => 'array'
];
} }

22
app/Models/ServerV2ray.php Executable file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerV2ray extends Model
{
protected $table = 'v2_server_v2ray';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'tlsSettings' => 'array',
'networkSettings' => 'array',
'dnsSettings' => 'array',
'ruleSettings' => 'array',
'tags' => 'array'
];
}

View File

@ -9,4 +9,8 @@ class StatOrder extends Model
protected $table = 'v2_stat_order'; protected $table = 'v2_stat_order';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class StatServer extends Model
protected $table = 'v2_stat_server'; protected $table = 'v2_stat_server';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class Ticket extends Model
protected $table = 'v2_ticket'; protected $table = 'v2_ticket';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class TicketMessage extends Model
protected $table = 'v2_ticket_message'; protected $table = 'v2_ticket_message';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -9,4 +9,8 @@ class User extends Model
protected $table = 'v2_user'; protected $table = 'v2_user';
protected $dateFormat = 'U'; protected $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp'
];
} }

View File

@ -8,6 +8,8 @@ namespace App\Payments;
use \Curl\Curl; use \Curl\Curl;
class MGate { class MGate {
private $config;
public function __construct($config) public function __construct($config)
{ {
$this->config = $config; $this->config = $config;
@ -47,6 +49,7 @@ class MGate {
$str = http_build_query($params) . $this->config['mgate_app_secret']; $str = http_build_query($params) . $this->config['mgate_app_secret'];
$params['sign'] = md5($str); $params['sign'] = md5($str);
$curl = new Curl(); $curl = new Curl();
$curl->setUserAgent('MGate');
$curl->post($this->config['mgate_url'] . '/v1/gateway/fetch', http_build_query($params)); $curl->post($this->config['mgate_url'] . '/v1/gateway/fetch', http_build_query($params));
$result = $curl->response; $result = $curl->response;
if (!$result) { if (!$result) {

View File

@ -0,0 +1,43 @@
<?php
namespace App\Providers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\HorizonApplicationServiceProvider;
class HorizonServiceProvider extends HorizonApplicationServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
parent::boot();
// Horizon::routeSmsNotificationsTo('15556667777');
// Horizon::routeMailNotificationsTo('example@example.com');
// Horizon::routeSlackNotificationsTo('slack-webhook-url', '#channel');
// Horizon::night();
}
/**
* Register the Horizon gate.
*
* This gate determines who can access Horizon in non-local environments.
*
* @return void
*/
protected function gate()
{
Gate::define('viewHorizon', function ($user) {
return in_array($user->email, [
//
]);
});
}
}

View File

@ -8,27 +8,20 @@ use Illuminate\Support\Facades\DB;
class CouponService class CouponService
{ {
public $order; public $coupon;
public $planId;
public $userId;
public function __construct($code) public function __construct($code)
{ {
$this->coupon = Coupon::where('code', $code)->first(); $this->coupon = Coupon::where('code', $code)->first();
if (!$this->coupon) {
abort(500, '优惠券无效');
}
if ($this->coupon->limit_use <= 0 && $this->coupon->limit_use !== NULL) {
abort(500, '优惠券已无可用次数');
}
if (time() < $this->coupon->started_at) {
abort(500, '优惠券还未到可用时间');
}
if (time() > $this->coupon->ended_at) {
abort(500, '优惠券已过期');
}
} }
public function use(Order $order) public function use(Order $order):bool
{ {
$this->setPlanId($order->plan_id);
$this->setUserId($order->user_id);
$this->check();
switch ($this->coupon->type) { switch ($this->coupon->type) {
case 1: case 1:
$order->discount_amount = $this->coupon->value; $order->discount_amount = $this->coupon->value;
@ -43,12 +36,6 @@ class CouponService
return false; return false;
} }
} }
if ($this->coupon->limit_plan_ids) {
$limitPlanIds = json_decode($this->coupon->limit_plan_ids);
if (!in_array($order->plan_id, $limitPlanIds)) {
return false;
}
}
return true; return true;
} }
@ -56,4 +43,57 @@ class CouponService
{ {
return $this->coupon->id; return $this->coupon->id;
} }
public function getCoupon()
{
return $this->coupon;
}
public function setPlanId($planId)
{
$this->planId = $planId;
}
public function setUserId($userId)
{
$this->userId = $userId;
}
public function checkLimitUseWithUser():bool
{
$usedCount = Order::where('coupon_id', $this->coupon->id)
->where('user_id', $this->userId)
->whereNotIn('status', [0, 2])
->count();
if ($usedCount >= $this->coupon->limit_use_with_user) return false;
return true;
}
public function check()
{
if (!$this->coupon) {
abort(500, __('Invalid coupon'));
}
if ($this->coupon->limit_use <= 0 && $this->coupon->limit_use !== NULL) {
abort(500, __('This coupon is no longer available'));
}
if (time() < $this->coupon->started_at) {
abort(500, __('This coupon has not yet started'));
}
if (time() > $this->coupon->ended_at) {
abort(500, __('This coupon has expired'));
}
if ($this->coupon->limit_plan_ids && $this->planId) {
if (!in_array($this->planId, $this->coupon->limit_plan_ids)) {
abort(500, __('The coupon code cannot be used for this subscription'));
}
}
if ($this->coupon->limit_use_with_user !== NULL && $this->userId) {
if (!$this->checkLimitUseWithUser()) {
abort(500, __('The coupon can only be used :limit_use_with_user per person', [
'limit_use_with_user' => $this->coupon->limit_use_with_user
]));
}
}
}
} }

View File

@ -12,13 +12,15 @@ class MailService
public function remindTraffic (User $user) public function remindTraffic (User $user)
{ {
if (!$user->remind_traffic) return; if (!$user->remind_traffic) return;
if (!$this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) return; if (!$this->remindTrafficIsWarnValue($user->u, $user->d, $user->transfer_enable)) return;
$flag = CacheKey::get('LAST_SEND_EMAIL_REMIND_TRAFFIC', $user->id); $flag = CacheKey::get('LAST_SEND_EMAIL_REMIND_TRAFFIC', $user->id);
if (Cache::get($flag)) return; if (Cache::get($flag)) return;
if (!Cache::put($flag, 1, 24 * 3600)) return; if (!Cache::put($flag, 1, 24 * 3600)) return;
SendEmailJob::dispatch([ SendEmailJob::dispatch([
'email' => $user->email, 'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%', 'subject' => __('The traffic usage in :app_name has reached 80%', [
'app_name' => config('v2board.app_name', 'V2board')
]),
'template_name' => 'remindTraffic', 'template_name' => 'remindTraffic',
'template_value' => [ 'template_value' => [
'name' => config('v2board.app_name', 'V2Board'), 'name' => config('v2board.app_name', 'V2Board'),
@ -27,8 +29,25 @@ class MailService
]); ]);
} }
private function remindTrafficIsWarnValue($ud, $transfer_enable) public function remindExpire(User $user)
{ {
if (!($user->expired_at !== NULL && ($user->expired_at - 86400) < time() && $user->expired_at > time())) return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => __('The service in :app_name is about to expire', [
'app_name' => config('v2board.app_name', 'V2board')
]),
'template_name' => 'remindExpire',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
private function remindTrafficIsWarnValue($u, $d, $transfer_enable)
{
$ud = $u + $d;
if (!$ud) return false; if (!$ud) return false;
if (!$transfer_enable) return false; if (!$transfer_enable) return false;
$percentage = ($ud / $transfer_enable) * 100; $percentage = ($ud / $transfer_enable) * 100;

View File

@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use App\Jobs\OrderHandleJob;
use App\Models\Order; use App\Models\Order;
use App\Models\Plan; use App\Models\Plan;
use App\Models\User; use App\Models\User;
@ -37,7 +38,7 @@ class OrderService
DB::beginTransaction(); DB::beginTransaction();
if ($order->surplus_order_ids) { if ($order->surplus_order_ids) {
try { try {
Order::whereIn('id', json_decode($order->surplus_order_ids))->update([ Order::whereIn('id', $order->surplus_order_ids)->update([
'status' => 4 'status' => 4
]); ]);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -81,25 +82,6 @@ class OrderService
DB::commit(); DB::commit();
} }
public function cancel():bool
{
$order = $this->order;
DB::beginTransaction();
$order->status = 2;
if (!$order->save()) {
DB::rollBack();
return false;
}
if ($order->balance_amount) {
$userService = new UserService();
if (!$userService->addBalance($order->user_id, $order->balance_amount)) {
DB::rollBack();
return false;
}
}
DB::commit();
return true;
}
public function setOrderType(User $user) public function setOrderType(User $user)
{ {
@ -190,13 +172,13 @@ class OrderService
$result = $trafficUnitPrice * $notUsedTraffic; $result = $trafficUnitPrice * $notUsedTraffic;
$orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3); $orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3);
$order->surplus_amount = $result > 0 ? $result : 0; $order->surplus_amount = $result > 0 ? $result : 0;
$order->surplus_order_ids = json_encode(array_column($orderModel->get()->toArray(), 'id')); $order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
} }
private function orderIsUsed(Order $order):bool private function orderIsUsed(Order $order):bool
{ {
$month = self::STR_TO_TIME[$order->cycle]; $month = self::STR_TO_TIME[$order->cycle];
$orderExpireDay = strtotime('+' . $month . ' month', $order->created_at->timestamp); $orderExpireDay = strtotime('+' . $month . ' month', $order->created_at);
if ($orderExpireDay < time()) return true; if ($orderExpireDay < time()) return true;
return false; return false;
} }
@ -229,20 +211,40 @@ class OrderService
return; return;
} }
$order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0; $order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
$order->surplus_order_ids = json_encode(array_column($orders->toArray(), 'id')); $order->surplus_order_ids = array_column($orders->toArray(), 'id');
} }
public function success(string $callbackNo) public function paid(string $callbackNo)
{ {
$order = $this->order; $order = $this->order;
if ($order->status !== 0) { if ($order->status !== 0) return true;
return true;
}
$order->status = 1; $order->status = 1;
$order->paid_at = time();
$order->callback_no = $callbackNo; $order->callback_no = $callbackNo;
return $order->save(); if (!$order->save()) return false;
OrderHandleJob::dispatch($order->trade_no);
return true;
} }
public function cancel():bool
{
$order = $this->order;
DB::beginTransaction();
$order->status = 2;
if (!$order->save()) {
DB::rollBack();
return false;
}
if ($order->balance_amount) {
$userService = new UserService();
if (!$userService->addBalance($order->user_id, $order->balance_amount)) {
DB::rollBack();
return false;
}
}
DB::commit();
return true;
}
private function buyByResetTraffic() private function buyByResetTraffic()
{ {

View File

@ -22,7 +22,7 @@ class PaymentService
if ($uuid) $payment = Payment::where('uuid', $uuid)->first()->toArray(); if ($uuid) $payment = Payment::where('uuid', $uuid)->first()->toArray();
$this->config = []; $this->config = [];
if (isset($payment)) { if (isset($payment)) {
$this->config = json_decode($payment['config'], true); $this->config = $payment['config'];
$this->config['enable'] = $payment['enable']; $this->config['enable'] = $payment['enable'];
$this->config['id'] = $payment['id']; $this->config['id'] = $payment['id'];
$this->config['uuid'] = $payment['uuid']; $this->config['uuid'] = $payment['uuid'];

View File

@ -5,7 +5,7 @@ namespace App\Services;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\User; use App\Models\User;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -18,14 +18,14 @@ class ServerService
public function getV2ray(User $user, $all = false):array public function getV2ray(User $user, $all = false):array
{ {
$servers = []; $servers = [];
$model = Server::orderBy('sort', 'ASC'); $model = ServerV2ray::orderBy('sort', 'ASC');
if (!$all) { if (!$all) {
$model->where('show', 1); $model->where('show', 1);
} }
$v2ray = $model->get(); $v2ray = $model->get();
for ($i = 0; $i < count($v2ray); $i++) { for ($i = 0; $i < count($v2ray); $i++) {
$v2ray[$i]['type'] = 'v2ray'; $v2ray[$i]['type'] = 'v2ray';
$groupId = json_decode($v2ray[$i]['group_id']); $groupId = $v2ray[$i]['group_id'];
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($v2ray[$i]['parent_id']) { if ($v2ray[$i]['parent_id']) {
$v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id'])); $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id']));
@ -50,7 +50,7 @@ class ServerService
$trojan = $model->get(); $trojan = $model->get();
for ($i = 0; $i < count($trojan); $i++) { for ($i = 0; $i < count($trojan); $i++) {
$trojan[$i]['type'] = 'trojan'; $trojan[$i]['type'] = 'trojan';
$groupId = json_decode($trojan[$i]['group_id']); $groupId = $trojan[$i]['group_id'];
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($trojan[$i]['parent_id']) { if ($trojan[$i]['parent_id']) {
$trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['parent_id'])); $trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['parent_id']));
@ -73,7 +73,7 @@ class ServerService
$shadowsocks = $model->get(); $shadowsocks = $model->get();
for ($i = 0; $i < count($shadowsocks); $i++) { for ($i = 0; $i < count($shadowsocks); $i++) {
$shadowsocks[$i]['type'] = 'shadowsocks'; $shadowsocks[$i]['type'] = 'shadowsocks';
$groupId = json_decode($shadowsocks[$i]['group_id']); $groupId = $shadowsocks[$i]['group_id'];
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($shadowsocks[$i]['parent_id']) { if ($shadowsocks[$i]['parent_id']) {
$shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id'])); $shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id']));
@ -123,7 +123,7 @@ class ServerService
public function getV2RayConfig(int $nodeId, int $localPort) public function getV2RayConfig(int $nodeId, int $localPort)
{ {
$server = Server::find($nodeId); $server = ServerV2ray::find($nodeId);
if (!$server) { if (!$server) {
abort(500, '节点不存在'); abort(500, '节点不存在');
} }
@ -156,10 +156,10 @@ class ServerService
return $json; return $json;
} }
private function setDns(Server $server, object $json) private function setDns(ServerV2ray $server, object $json)
{ {
if ($server->dnsSettings) { if ($server->dnsSettings) {
$dns = json_decode($server->dnsSettings); $dns = $server->dnsSettings;
if (isset($dns->servers)) { if (isset($dns->servers)) {
array_push($dns->servers, '1.1.1.1'); array_push($dns->servers, '1.1.1.1');
array_push($dns->servers, 'localhost'); array_push($dns->servers, 'localhost');
@ -169,41 +169,41 @@ class ServerService
} }
} }
private function setNetwork(Server $server, object $json) private function setNetwork(ServerV2ray $server, object $json)
{ {
if ($server->networkSettings) { if ($server->networkSettings) {
switch ($server->network) { switch ($server->network) {
case 'tcp': case 'tcp':
$json->inbound->streamSettings->tcpSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->tcpSettings = $server->networkSettings;
break; break;
case 'kcp': case 'kcp':
$json->inbound->streamSettings->kcpSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->kcpSettings = $server->networkSettings;
break; break;
case 'ws': case 'ws':
$json->inbound->streamSettings->wsSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->wsSettings = $server->networkSettings;
break; break;
case 'http': case 'http':
$json->inbound->streamSettings->httpSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->httpSettings = $server->networkSettings;
break; break;
case 'domainsocket': case 'domainsocket':
$json->inbound->streamSettings->dsSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->dsSettings = $server->networkSettings;
break; break;
case 'quic': case 'quic':
$json->inbound->streamSettings->quicSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->quicSettings = $server->networkSettings;
break; break;
case 'grpc': case 'grpc':
$json->inbound->streamSettings->grpcSettings = json_decode($server->networkSettings); $json->inbound->streamSettings->grpcSettings = $server->networkSettings;
break; break;
} }
} }
} }
private function setRule(Server $server, object $json) private function setRule(ServerV2ray $server, object $json)
{ {
$domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain'))); $domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain')));
$protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol'))); $protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol')));
if ($server->ruleSettings) { if ($server->ruleSettings) {
$ruleSettings = json_decode($server->ruleSettings); $ruleSettings = $server->ruleSettings;
// domain // domain
if (isset($ruleSettings->domain)) { if (isset($ruleSettings->domain)) {
$ruleSettings->domain = array_filter($ruleSettings->domain); $ruleSettings->domain = array_filter($ruleSettings->domain);
@ -238,10 +238,10 @@ class ServerService
} }
} }
private function setTls(Server $server, object $json) private function setTls(ServerV2ray $server, object $json)
{ {
if ((int)$server->tls) { if ((int)$server->tls) {
$tlsSettings = json_decode($server->tlsSettings); $tlsSettings = $server->tlsSettings;
$json->inbound->streamSettings->security = 'tls'; $json->inbound->streamSettings->security = 'tls';
$tls = (object)[ $tls = (object)[
'certificateFile' => '/root/.cert/server.crt', 'certificateFile' => '/root/.cert/server.crt',
@ -260,20 +260,23 @@ class ServerService
public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method) public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
{ {
if (($u + $d) <= 10240) return; if (($u + $d) < 10240) return true;
$timestamp = strtotime(date('Y-m-d H:0')); $timestamp = strtotime(date('Y-m-d'));
$serverLog = ServerLog::where('log_at', '>=', $timestamp) $serverLog = ServerLog::where('log_at', '>=', $timestamp)
->where('log_at', '<', $timestamp + 3600) ->where('log_at', '<', $timestamp + 3600)
->where('server_id', $serverId) ->where('server_id', $serverId)
->where('user_id', $userId) ->where('user_id', $userId)
->where('rate', $rate) ->where('rate', $rate)
->where('method', $method) ->where('method', $method)
->lockForUpdate()
->first(); ->first();
if ($serverLog) { if ($serverLog) {
$serverLog->u = $serverLog->u + $u; try {
$serverLog->d = $serverLog->d + $d; $serverLog->increment('u', $u);
$serverLog->save(); $serverLog->increment('d', $d);
return true;
} catch (\Exception $e) {
return false;
}
} else { } else {
$serverLog = new ServerLog(); $serverLog = new ServerLog();
$serverLog->user_id = $userId; $serverLog->user_id = $userId;
@ -283,7 +286,7 @@ class ServerService
$serverLog->rate = $rate; $serverLog->rate = $rate;
$serverLog->log_at = $timestamp; $serverLog->log_at = $timestamp;
$serverLog->method = $method; $serverLog->method = $method;
$serverLog->save(); return $serverLog->save();
} }
} }
@ -292,32 +295,15 @@ class ServerService
$server = ServerShadowsocks::orderBy('sort', 'ASC')->get(); $server = ServerShadowsocks::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) { for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'shadowsocks'; $server[$i]['type'] = 'shadowsocks';
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
} }
return $server->toArray(); return $server->toArray();
} }
public function getV2rayServers() public function getV2rayServers()
{ {
$server = Server::orderBy('sort', 'ASC')->get(); $server = ServerV2ray::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) { for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'v2ray'; $server[$i]['type'] = 'v2ray';
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
if (!empty($server[$i]['dnsSettings'])) {
$server[$i]['dnsSettings'] = json_decode($server[$i]['dnsSettings']);
}
if (!empty($server[$i]['tlsSettings'])) {
$server[$i]['tlsSettings'] = json_decode($server[$i]['tlsSettings']);
}
if (!empty($server[$i]['ruleSettings'])) {
$server[$i]['ruleSettings'] = json_decode($server[$i]['ruleSettings']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
} }
return $server->toArray(); return $server->toArray();
} }
@ -327,10 +313,6 @@ class ServerService
$server = ServerTrojan::orderBy('sort', 'ASC')->get(); $server = ServerTrojan::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) { for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'trojan'; $server[$i]['type'] = 'trojan';
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
} }
return $server->toArray(); return $server->toArray();
} }

View File

@ -40,6 +40,7 @@ class TelegramService {
$curl->get($this->api . $method . '?' . http_build_query($params)); $curl->get($this->api . $method . '?' . http_build_query($params));
$response = $curl->response; $response = $curl->response;
$curl->close(); $curl->close();
if (!isset($response->ok)) abort(500, '请求失败');
if (!$response->ok) { if (!$response->ok) {
abort(500, '来自TG的错误' . $response->description); abort(500, '来自TG的错误' . $response->description);
} }

View File

@ -12,6 +12,7 @@ use Illuminate\Support\Facades\DB;
class TicketService { class TicketService {
public function replyByAdmin($ticketId, $message, $userId):void public function replyByAdmin($ticketId, $message, $userId):void
{ {
if ($message)
$ticket = Ticket::where('id', $ticketId) $ticket = Ticket::where('id', $ticketId)
->first(); ->first();
if (!$ticket) { if (!$ticket) {

View File

@ -2,9 +2,11 @@
namespace App\Services; namespace App\Services;
use App\Jobs\ServerLogJob;
use App\Jobs\TrafficFetchJob;
use App\Models\InviteCode; use App\Models\InviteCode;
use App\Models\Order; use App\Models\Order;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\Ticket; use App\Models\Ticket;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
@ -80,33 +82,9 @@ class UserService
return true; return true;
} }
public function trafficFetch(int $u, int $d, int $userId, object $server, string $protocol):bool public function trafficFetch(int $u, int $d, int $userId, object $server, string $protocol)
{ {
$user = User::lockForUpdate() TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
->find($userId); ServerLogJob::dispatch($u, $d, $userId, $server, $protocol);
if (!$user) {
return true;
}
$user->t = time();
$user->u = $user->u + $u;
$user->d = $user->d + $d;
if (!$user->save()) {
return false;
}
$mailService = new MailService();
$serverService = new ServerService();
try {
$mailService->remindTraffic($user);
$serverService->log(
$userId,
$server->id,
$u,
$d,
$server->rate,
$protocol
);
} catch (\Exception $e) {
}
return true;
} }
} }

View File

@ -17,7 +17,7 @@ class CacheKey
'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间', 'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间', 'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间',
'TEMP_TOKEN' => '临时令牌', 'TEMP_TOKEN' => '临时令牌',
'LAST_SEND_EMAIL_REMIND_TRAFFIC' 'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒'
]; ];
public static function get(string $key, $uniqueValue) public static function get(string $key, $uniqueValue)

View File

@ -2,7 +2,7 @@
namespace App\Utils; namespace App\Utils;
use App\Models\Server; use App\Models\ServerV2ray;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Models\User; use App\Models\User;
@ -23,6 +23,12 @@ class Helper
return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . '-' . time()); return md5(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)) . '-' . time());
} }
public static function generateOrderNo(): string
{
$randomChar = rand(10000, 99999);
return date('YmdHms') . $randomChar;
}
public static function exchange($from, $to) public static function exchange($from, $to)
{ {
$result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from); $result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
@ -58,11 +64,12 @@ class Helper
return $str; return $str;
} }
public static function multiPasswordVerify($algo, $password, $hash) public static function multiPasswordVerify($algo, $salt, $password, $hash)
{ {
switch($algo) { switch($algo) {
case 'md5': return md5($password) === $hash; case 'md5': return md5($password) === $hash;
case 'sha256': return hash('sha256', $password) === $hash; case 'sha256': return hash('sha256', $password) === $hash;
case 'md5salt': return md5($password . $salt) === $hash;
default: return password_verify($password, $hash); default: return password_verify($password, $hash);
} }
} }
@ -95,4 +102,14 @@ class Helper
return round($byte, 2) . ' B'; return round($byte, 2) . ' B';
} }
} }
public static function getSubscribeHost()
{
$subscribeUrl = config('v2board.app_url');
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
if ($subscribeUrls && $subscribeUrls[0]) {
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
}
return $subscribeUrl;
}
} }

View File

@ -1,31 +1,37 @@
{ {
"name": "v2board/v2board", "name": "v2board/v2board",
"type": "project", "type": "project",
"description": "v2board is a v2ray manage.", "description": "v2board is a proxy protocol manage.",
"keywords": [ "keywords": [
"v2board", "v2board",
"v2ray", "v2ray",
"shadowsocks",
"trojan",
"laravel" "laravel"
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.2", "php": "^7.2.5|^8.0",
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.4",
"fruitcake/laravel-cors": "^2.0",
"google/recaptcha": "^1.2", "google/recaptcha": "^1.2",
"laravel/framework": "^6.0", "guzzlehttp/guzzle": "^6.3.1|^7.0.1",
"laravel/tinker": "^1.0", "laravel/framework": "^7.29",
"lokielse/omnipay-alipay": "3.0.6", "laravel/horizon": "^4.3.5",
"laravel/tinker": "^2.5",
"linfo/linfo": "^4.0",
"lokielse/omnipay-alipay": "3.1.2",
"lokielse/omnipay-wechatpay": "^3.0", "lokielse/omnipay-wechatpay": "^3.0",
"php-curl-class/php-curl-class": "^8.6", "php-curl-class/php-curl-class": "^8.6",
"stripe/stripe-php": "^7.36.1", "stripe/stripe-php": "^7.36.1",
"symfony/yaml": "^4.3" "symfony/yaml": "^4.3"
}, },
"require-dev": { "require-dev": {
"facade/ignition": "^1.4", "facade/ignition": "^2.0",
"fzaninotto/faker": "^1.4", "fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^3.0", "nunomaduro/collision": "^4.3",
"phpunit/phpunit": "^8.0" "phpunit/phpunit": "^8.5.8|^9.3.3"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,

View File

@ -173,6 +173,7 @@ return [
App\Providers\AuthServiceProvider::class, App\Providers\AuthServiceProvider::class,
// App\Providers\BroadcastServiceProvider::class, // App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
], ],
@ -236,5 +237,5 @@ return [
| The only modification by laravel config | The only modification by laravel config
| |
*/ */
'version' => '1.5.2.1627559775390' 'version' => '1.5.3.1628409393360'
]; ];

34
config/cors.php Normal file
View File

@ -0,0 +1,34 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Cross-Origin Resource Sharing (CORS) Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your settings for cross-origin resource sharing
| or "CORS". This determines what cross-origin operations may execute
| in web browsers. You are free to adjust these settings as needed.
|
| To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
|
*/
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,
];

191
config/horizon.php Normal file
View File

@ -0,0 +1,191 @@
<?php
use Illuminate\Support\Str;
use Linfo\Linfo;
$lInfo = new Linfo();
$parser = $lInfo->getParser();
return [
/*
|--------------------------------------------------------------------------
| Horizon Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Horizon will be accessible from. If this
| setting is null, Horizon will reside under the same domain as the
| application. Otherwise, this value will serve as the subdomain.
|
*/
'domain' => null,
/*
|--------------------------------------------------------------------------
| Horizon Path
|--------------------------------------------------------------------------
|
| This is the URI path where Horizon will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => 'monitor',
/*
|--------------------------------------------------------------------------
| Horizon Redis Connection
|--------------------------------------------------------------------------
|
| This is the name of the Redis connection where Horizon will store the
| meta information required for it to function. It includes the list
| of supervisors, failed jobs, job metrics, and other information.
|
*/
'use' => 'default',
/*
|--------------------------------------------------------------------------
| Horizon Redis Prefix
|--------------------------------------------------------------------------
|
| This prefix will be used when storing all Horizon data in Redis. You
| may modify the prefix when you are running multiple installations
| of Horizon on the same server so that they don't have problems.
|
*/
'prefix' => env(
'HORIZON_PREFIX',
Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:'
),
/*
|--------------------------------------------------------------------------
| Horizon Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will get attached onto each Horizon route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => ['web', 'admin'],
/*
|--------------------------------------------------------------------------
| Queue Wait Time Thresholds
|--------------------------------------------------------------------------
|
| This option allows you to configure when the LongWaitDetected event
| will be fired. Every connection / queue combination may have its
| own, unique threshold (in seconds) before this event is fired.
|
*/
'waits' => [
'redis:default' => 60,
],
/*
|--------------------------------------------------------------------------
| Job Trimming Times
|--------------------------------------------------------------------------
|
| Here you can configure for how long (in minutes) you desire Horizon to
| persist the recent and failed jobs. Typically, recent jobs are kept
| for one hour while all failed jobs are stored for an entire week.
|
*/
'trim' => [
'recent' => 60,
'pending' => 60,
'completed' => 60,
'recent_failed' => 10080,
'failed' => 10080,
'monitored' => 10080,
],
/*
|--------------------------------------------------------------------------
| Metrics
|--------------------------------------------------------------------------
|
| Here you can configure how many snapshots should be kept to display in
| the metrics graph. This will get used in combination with Horizon's
| `horizon:snapshot` schedule to define how long to retain metrics.
|
*/
'metrics' => [
'trim_snapshots' => [
'job' => 24,
'queue' => 24,
],
],
/*
|--------------------------------------------------------------------------
| Fast Termination
|--------------------------------------------------------------------------
|
| When this option is enabled, Horizon's "terminate" command will not
| wait on all of the workers to terminate unless the --wait option
| is provided. Fast termination can shorten deployment delay by
| allowing a new instance of Horizon to start while the last
| instance will continue to terminate each of its workers.
|
*/
'fast_termination' => false,
/*
|--------------------------------------------------------------------------
| Memory Limit (MB)
|--------------------------------------------------------------------------
|
| This value describes the maximum amount of memory the Horizon worker
| may consume before it is terminated and restarted. You should set
| this value according to the resources available to your server.
|
*/
'memory_limit' => 32,
/*
|--------------------------------------------------------------------------
| Queue Worker Configuration
|--------------------------------------------------------------------------
|
| Here you may define the queue worker settings used by your application
| in all environments. These supervisors and settings handle all your
| queued jobs and will be provisioned by Horizon during deployment.
|
*/
'environments' => [
'local' => [
'V2board' => [
'connection' => 'redis',
'queue' => [
'traffic_fetch',
'server_log',
'send_email',
'send_telegram',
'stat_server',
'order_handle'
],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => (int)ceil($parser->getRam()['total'] / 1024 / 1024 / 1024 * 6),
'tries' => 1,
'nice' => 0,
],
],
],
];

View File

@ -19,6 +19,20 @@ CREATE TABLE `failed_jobs` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DROP TABLE IF EXISTS `v2_commission_log`;
CREATE TABLE `v2_commission_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invite_user_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`trade_no` char(36) NOT NULL,
`order_amount` int(11) NOT NULL,
`get_amount` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_coupon`; DROP TABLE IF EXISTS `v2_coupon`;
CREATE TABLE `v2_coupon` ( CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -27,6 +41,7 @@ CREATE TABLE `v2_coupon` (
`type` tinyint(1) NOT NULL, `type` tinyint(1) NOT NULL,
`value` int(11) NOT NULL, `value` int(11) NOT NULL,
`limit_use` int(11) DEFAULT NULL, `limit_use` int(11) DEFAULT NULL,
`limit_use_with_user` int(11) DEFAULT NULL,
`limit_plan_ids` varchar(255) DEFAULT NULL, `limit_plan_ids` varchar(255) DEFAULT NULL,
`started_at` int(11) NOT NULL, `started_at` int(11) NOT NULL,
`ended_at` int(11) NOT NULL, `ended_at` int(11) NOT NULL,
@ -110,6 +125,7 @@ CREATE TABLE `v2_order` (
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付1开通中2已取消3已完成4已折抵', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待支付1开通中2已取消3已完成4已折抵',
`commission_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待确认1发放中2有效3无效', `commission_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待确认1发放中2有效3无效',
`commission_balance` int(11) NOT NULL DEFAULT '0', `commission_balance` int(11) NOT NULL DEFAULT '0',
`paid_at` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@ -149,40 +165,13 @@ CREATE TABLE `v2_plan` (
`three_year_price` int(11) DEFAULT NULL, `three_year_price` int(11) DEFAULT NULL,
`onetime_price` int(11) DEFAULT NULL, `onetime_price` int(11) DEFAULT NULL,
`reset_price` int(11) DEFAULT NULL, `reset_price` int(11) DEFAULT NULL,
`reset_traffic_method` tinyint(1) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server`;
CREATE TABLE `v2_server` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`network` text NOT NULL,
`alter_id` int(11) NOT NULL DEFAULT '1',
`settings` text,
`rules` text,
`networkSettings` text,
`tlsSettings` text,
`ruleSettings` text,
`dnsSettings` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_group`; DROP TABLE IF EXISTS `v2_server_group`;
CREATE TABLE `v2_server_group` ( CREATE TABLE `v2_server_group` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -206,7 +195,9 @@ CREATE TABLE `v2_server_log` (
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `log_at` (`log_at`) KEY `log_at` (`log_at`),
KEY `user_id` (`user_id`),
KEY `server_id` (`server_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -251,6 +242,34 @@ CREATE TABLE `v2_server_trojan` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表';
DROP TABLE IF EXISTS `v2_server_v2ray`;
CREATE TABLE `v2_server_v2ray` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`network` text NOT NULL,
`alter_id` int(11) NOT NULL DEFAULT '1',
`settings` text,
`rules` text,
`networkSettings` text,
`tlsSettings` text,
`ruleSettings` text,
`dnsSettings` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_stat_order`; DROP TABLE IF EXISTS `v2_stat_order`;
CREATE TABLE `v2_stat_order` ( CREATE TABLE `v2_stat_order` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -304,7 +323,7 @@ CREATE TABLE `v2_ticket_message` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL, `user_id` int(11) NOT NULL,
`ticket_id` int(11) NOT NULL, `ticket_id` int(11) NOT NULL,
`message` varchar(255) NOT NULL, `message` text CHARACTER SET utf8mb4 NOT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@ -319,6 +338,7 @@ CREATE TABLE `v2_user` (
`email` varchar(64) NOT NULL, `email` varchar(64) NOT NULL,
`password` varchar(64) NOT NULL, `password` varchar(64) NOT NULL,
`password_algo` char(10) DEFAULT NULL, `password_algo` char(10) DEFAULT NULL,
`password_salt` char(10) DEFAULT NULL,
`balance` int(11) NOT NULL DEFAULT '0', `balance` int(11) NOT NULL DEFAULT '0',
`discount` int(11) DEFAULT NULL, `discount` int(11) DEFAULT NULL,
`commission_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime', `commission_type` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime',
@ -336,8 +356,8 @@ CREATE TABLE `v2_user` (
`uuid` varchar(36) NOT NULL, `uuid` varchar(36) NOT NULL,
`group_id` int(11) DEFAULT NULL, `group_id` int(11) DEFAULT NULL,
`plan_id` int(11) DEFAULT NULL, `plan_id` int(11) DEFAULT NULL,
`remind_expire` tinyint(4) DEFAULT '1', `remind_expire` tinyint(4) DEFAULT '0',
`remind_traffic` tinyint(4) DEFAULT '1', `remind_traffic` tinyint(4) DEFAULT '0',
`token` char(32) NOT NULL, `token` char(32) NOT NULL,
`remarks` text, `remarks` text,
`expired_at` bigint(20) DEFAULT '0', `expired_at` bigint(20) DEFAULT '0',
@ -348,4 +368,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2021-07-13 13:50:52 -- 2021-09-21 10:07:22

View File

@ -425,3 +425,40 @@ DROP INDEX `email_deleted_at`;
ALTER TABLE `v2_user` ALTER TABLE `v2_user`
ADD `commission_type` tinyint NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime' AFTER `discount`; ADD `commission_type` tinyint NOT NULL DEFAULT '0' COMMENT '0: system 1: cycle 2: onetime' AFTER `discount`;
ALTER TABLE `v2_order`
ADD `paid_at` int(11) NULL AFTER `commission_balance`;
ALTER TABLE `v2_server_log`
ADD INDEX `user_id` (`user_id`),
ADD INDEX `server_id` (`server_id`);
ALTER TABLE `v2_ticket_message`
CHANGE `message` `message` text COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `ticket_id`;
ALTER TABLE `v2_coupon`
ADD `limit_use_with_user` int(11) NULL AFTER `limit_use`;
ALTER TABLE `v2_user`
ADD `password_salt` char(10) COLLATE 'utf8_general_ci' NULL AFTER `password_algo`;
CREATE TABLE `v2_commission_log` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`invite_user_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`trade_no` char(36) NOT NULL,
`order_amount` int(11) NOT NULL,
`get_amount` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
) COLLATE 'utf8mb4_general_ci';
ALTER TABLE `v2_plan`
ADD `reset_traffic_method` tinyint(1) NULL AFTER `reset_price`;
ALTER TABLE `v2_server`
RENAME TO `v2_server_v2ray`;
ALTER TABLE `v2_user`
CHANGE `remind_expire` `remind_expire` tinyint(4) NULL DEFAULT '0' AFTER `plan_id`,
CHANGE `remind_traffic` `remind_traffic` tinyint(4) NULL DEFAULT '0' AFTER `remind_expire`;

View File

@ -1,3 +1,4 @@
rm -rf composer.phar
wget https://getcomposer.org/download/2.0.13/composer.phar wget https://getcomposer.org/download/2.0.13/composer.phar
php composer.phar install -vvv php composer.phar install -vvv
php artisan v2board:install php artisan v2board:install

View File

@ -1,5 +1,5 @@
apps: apps:
- name : 'V2Board' - name : 'V2Board'
script : 'php artisan queue:work --queue=send_email,send_telegram,stat_server' script : 'php artisan horizon'
instances: 4 instances: 1
out_file : './storage/logs/queue/queue.log' out_file : './storage/logs/queue/queue.log'

4
public/assets/admin/theme/green.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More