Compare commits

..

No commits in common. "master" and "1.6.1" have entirely different histories.

129 changed files with 934 additions and 2854 deletions

View File

@ -14,7 +14,7 @@ DB_USERNAME=root
DB_PASSWORD=123456
BROADCAST_DRIVER=log
CACHE_DRIVER=file
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis
SESSION_LIFETIME=120

View File

@ -1,39 +0,0 @@
---
name: Bug report | 问题反馈
about: Tell us what problems you have encountered
title: "[BUG]"
labels: ''
assignees: ''
---
🙇🙇🙇注意XrayR等非V2Board问题请前往项目方提问
🙇🙇🙇Note: XrayR and other non-V2Board issues please go to the project side to ask questions
The V2Board version number you are using
当前使用的V2Board版本号
--------
Briefly describe the problem you are experiencing
简单描述你遇到的问题
--------
Screenshot of the reported error(Please do desensitization)
报告错误的截图(请做脱敏处理)
--------
Screenshot of the reported error(Please do desensitization)
报告错误的截图(请做脱敏处理)
--------
The latest log files in the storage/logs directory report from #1 (Please do desensitization)
storage/logs 目录下最新的日志文件从 #1 开始报告(请做脱敏处理)
--------

View File

@ -1,11 +0,0 @@
---
name: Feature request | 功能请求
about: Tell us what you need
title: "[Feature request]"
labels: ''
assignees: ''
---
Please describe in detail the problems or needs you have encountered.
请详细描述你遇到的问题或需求。

View File

@ -50,7 +50,7 @@ class CheckCommission extends Command
if ((int)config('v2board.commission_auto_check_enable', 1)) {
Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->where('status', 3)
->whereNotIn('status', [0, 2])
->where('updated_at', '<=', strtotime('-3 day', time()))
->update([
'commission_status' => 1
@ -80,14 +80,15 @@ class CheckCommission extends Command
public function payHandle($inviteUserId, Order $order)
{
$level = 3;
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
];

View File

@ -2,9 +2,7 @@
namespace App\Console\Commands;
use App\Models\Log;
use App\Models\Plan;
use App\Models\StatServer;
use App\Models\StatUser;
use App\Utils\Helper;
use Illuminate\Console\Command;
@ -45,8 +43,7 @@ class ResetLog extends Command
*/
public function handle()
{
StatUser::where('record_at', '<', strtotime('-2 month', time()))->delete();
StatServer::where('record_at', '<', strtotime('-2 month', time()))->delete();
Log::where('created_at', '<', strtotime('-1 month', time()))->delete();
StatUser::where('record_at', '<', strtotime('-2 month', time()))
->delete();
}
}

View File

@ -1,58 +0,0 @@
<?php
namespace App\Console\Commands;
use App\Models\Plan;
use App\Utils\Helper;
use Illuminate\Console\Command;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class ResetUser extends Command
{
protected $builder;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'reset:user';
/**
* The console command description.
*
* @var string
*/
protected $description = '重置所有用户信息';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (!$this->confirm("确定要重置所有用户安全信息吗?")) {
return;
}
ini_set('memory_limit', -1);
$users = User::all();
foreach ($users as $user)
{
$user->token = Helper::guid();
$user->uuid = Helper::guid(true);
$user->save();
$this->info("已重置用户{$user->email}的安全信息");
}
}
}

View File

@ -45,7 +45,6 @@ class SendRemindMail extends Command
$mailService = new MailService();
foreach ($users as $user) {
if ($user->remind_expire) $mailService->remindExpire($user);
if ($user->remind_traffic) $mailService->remindTraffic($user);
}
}
}

View File

@ -48,9 +48,7 @@ class V2boardInstall extends Command
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.env')) {
$securePath = config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key'))));
$this->info("访问 http(s)://你的站点/{$securePath} 进入管理面板,你可以在用户中心修改你的密码。");
abort(500, '如需重新安装请删除目录下.env文件');
abort(500, 'V2board 已安装,如需重新安装请删除目录下.env文件');
}
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
@ -91,17 +89,16 @@ class V2boardInstall extends Command
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
}
$password = Helper::guid(false);
$password = '';
while (!$password) {
$password = $this->ask('请输入管理员密码?');
}
if (!$this->registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
}
$this->info('一切就绪');
$this->info("管理员邮箱:{$email}");
$this->info("管理员密码:{$password}");
$defaultSecurePath = hash('crc32b', config('app.key'));
$this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以在用户中心修改你的密码。");
$this->info('访问 http(s)://你的站点/admin 进入管理面板');
} catch (\Exception $e) {
$this->error($e->getMessage());
}

View File

@ -2,15 +2,10 @@
namespace App\Console\Commands;
use App\Models\StatServer;
use App\Models\StatUser;
use App\Models\User;
use App\Services\StatisticalService;
use Illuminate\Console\Command;
use App\Models\Order;
use App\Models\Stat;
use App\Models\StatOrder;
use App\Models\CommissionLog;
use Illuminate\Support\Facades\DB;
class V2boardStatistics extends Command
{
@ -45,87 +40,38 @@ class V2boardStatistics extends Command
*/
public function handle()
{
$startAt = microtime(true);
ini_set('memory_limit', -1);
$this->statUser();
$this->statServer();
$this->stat();
$this->info('耗时' . (microtime(true) - $startAt));
$this->statOrder();
}
private function statServer()
{
$createdAt = time();
$recordAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$statService = new StatisticalService();
$statService->setStartAt($recordAt);
$statService->setServerStats();
$stats = $statService->getStatServer();
DB::beginTransaction();
foreach ($stats as $stat) {
if (!StatServer::insert([
'server_id' => $stat['server_id'],
'server_type' => $stat['server_type'],
'u' => $stat['u'],
'd' => $stat['d'],
'created_at' => $createdAt,
'updated_at' => $createdAt,
'record_type' => 'd',
'record_at' => $recordAt
])) {
DB::rollback();
throw new \Exception('stat server fail');
}
}
DB::commit();
$statService->clearStatServer();
}
private function statUser()
{
$createdAt = time();
$recordAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$statService = new StatisticalService();
$statService->setStartAt($recordAt);
$statService->setUserStats();
$stats = $statService->getStatUser();
DB::beginTransaction();
foreach ($stats as $stat) {
if (!StatUser::insert([
'user_id' => $stat['user_id'],
'u' => $stat['u'],
'd' => $stat['d'],
'server_rate' => $stat['server_rate'],
'created_at' => $createdAt,
'updated_at' => $createdAt,
'record_type' => 'd',
'record_at' => $recordAt
])) {
DB::rollback();
throw new \Exception('stat user fail');
}
}
DB::commit();
$statService->clearStatUser();
}
private function stat()
private function statOrder()
{
$endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt);
$statisticalService = new StatisticalService();
$statisticalService->setStartAt($startAt);
$statisticalService->setEndAt($endAt);
$data = $statisticalService->generateStatData();
$data['record_at'] = $startAt;
$data['record_type'] = 'd';
$statistic = Stat::where('record_at', $startAt)
$orderBuilder = Order::where('paid_at', '>=', $startAt)
->where('paid_at', '<', $endAt)
->whereNotIn('status', [0, 2]);
$orderCount = $orderBuilder->count();
$orderAmount = $orderBuilder->sum('total_amount');
$commissionBuilder = Order::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt);
$commissionCount = $commissionBuilder->count();
$commissionAmount = $commissionBuilder->sum('actual_commission_balance');
$data = [
'order_count' => $orderCount,
'order_amount' => $orderAmount,
'commission_count' => $commissionCount,
'commission_amount' => $commissionAmount,
'record_type' => 'd',
'record_at' => $startAt
];
$statistic = StatOrder::where('record_at', $startAt)
->where('record_type', 'd')
->first();
if ($statistic) {
$statistic->update($data);
return;
}
Stat::create($data);
StatOrder::create($data);
}
}

View File

@ -57,7 +57,6 @@ class V2boardUpdate extends Command
} catch (\Exception $e) {
}
}
\Artisan::call('horizon:terminate');
$this->info('更新完毕,队列服务已重启,你无需进行任何操作。');
$this->info('更新完毕,请重新启动队列服务。');
}
}

View File

@ -54,7 +54,9 @@ class Handler extends ExceptionHandler
public function render($request, Throwable $exception)
{
if ($exception instanceof ViewException) {
abort(500, "主题渲染失败。如更新主题,参数可能发生变化请重新配置主题后再试。");
return response([
'message' => "主题初始化发生错误,请在后台对主题检查或配置后重试。"
]);
}
return parent::render($request, $exception);
}

View File

@ -87,16 +87,27 @@ class ConfigController extends Controller
'site' => [
'logo' => config('v2board.logo'),
'force_https' => (int)config('v2board.force_https', 0),
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
'stop_register' => (int)config('v2board.stop_register', 0),
'email_verify' => (int)config('v2board.email_verify', 0),
'app_name' => config('v2board.app_name', 'V2Board'),
'app_description' => config('v2board.app_description', 'V2Board is best!'),
'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
'try_out_hour' => (int)config('v2board.try_out_hour', 1),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
'recaptcha_key' => config('v2board.recaptcha_key'),
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
'tos_url' => config('v2board.tos_url'),
'currency' => config('v2board.currency', 'CNY'),
'currency_symbol' => config('v2board.currency_symbol', '¥'),
'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0),
'register_limit_count' => config('v2board.register_limit_count', 3),
'register_limit_expire' => config('v2board.register_limit_expire', 60)
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
@ -113,11 +124,14 @@ class ConfigController extends Controller
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),
'frontend_background_url' => config('v2board.frontend_background_url'),
'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin')
],
'server' => [
'server_token' => config('v2board.server_token'),
'server_pull_interval' => config('v2board.server_pull_interval', 60),
'server_push_interval' => config('v2board.server_push_interval', 60),
'server_license' => config('v2board.server_license'),
'server_log_enable' => config('v2board.server_log_enable', 0),
'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
],
'email' => [
'email_template' => config('v2board.email_template', 'default'),
@ -140,23 +154,6 @@ class ConfigController extends Controller
'macos_download_url' => config('v2board.macos_download_url'),
'android_version' => config('v2board.android_version'),
'android_download_url' => config('v2board.android_download_url')
],
'safe' => [
'email_verify' => (int)config('v2board.email_verify', 0),
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key')))),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
'recaptcha_key' => config('v2board.recaptcha_key'),
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0),
'register_limit_count' => config('v2board.register_limit_count', 3),
'register_limit_expire' => config('v2board.register_limit_expire', 60),
'password_limit_enable' => (int)config('v2board.password_limit_enable', 1),
'password_limit_count' => config('v2board.password_limit_count', 5),
'password_limit_expire' => config('v2board.password_limit_expire', 60)
]
];
if ($key && isset($data[$key])) {

View File

@ -82,7 +82,6 @@ class CouponController extends Controller
$coupons = [];
$coupon = $request->validated();
$coupon['created_at'] = $coupon['updated_at'] = time();
$coupon['show'] = 1;
unset($coupon['generate_count']);
for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon['code'] = Helper::randomChar(8);

View File

@ -41,13 +41,10 @@ class PlanController extends Controller
DB::beginTransaction();
// update user group id and transfer
try {
if ($request->input('force_update')) {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824,
'speed_limit' => $params['speed_limit']
]);
}
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824
]);
$plan->update($params);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Admin\Server;
use App\Models\Plan;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\ServerGroup;
use App\Models\User;
use App\Services\ServerService;
@ -65,7 +65,7 @@ class GroupController extends Controller
}
}
$servers = ServerVmess::all();
$servers = ServerV2ray::all();
foreach ($servers as $server) {
if (in_array($request->input('id'), $server->group_id)) {
abort(500, '该组已被节点所使用,无法删除');

View File

@ -1,113 +0,0 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerVmessSave;
use App\Http\Requests\Admin\ServerVmessUpdate;
use App\Models\ServerHysteria;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerVmess;
class HysteriaController extends Controller
{
public function save(Request $request)
{
$params = $request->validate([
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tags' => 'nullable|array',
'rate' => 'required|numeric',
'up_mbps' => 'required|numeric|min:1',
'down_mbps' => 'required|numeric|min:1',
'server_name' => 'nullable',
'insecure' => 'required|in:0,1'
]);
if ($request->input('id')) {
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerHysteria::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(Request $request)
{
$request->validate([
'show' => 'in:0,1'
], [
'show.in' => '显示状态格式不正确'
]);
$params = $request->only([
'show',
]);
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerHysteria::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerHysteria::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@ -2,6 +2,9 @@
namespace App\Http\Controllers\Admin\Server;
use App\Models\ServerV2ray;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
@ -20,20 +23,27 @@ class ManageController extends Controller
public function sort(Request $request)
{
ini_set('post_max_size', '1m');
$params = $request->only(
'shadowsocks',
'vmess',
'trojan',
'hysteria'
) ?? [];
DB::beginTransaction();
foreach ($params as $k => $v) {
$model = 'App\\Models\\Server' . ucfirst($k);
foreach($v as $id => $sort) {
if (!$model::find($id)->update(['sort' => $sort])) {
DB::rollBack();
abort(500, '保存失败');
}
foreach ($request->input('sorts') as $k => $v) {
switch ($v['key']) {
case 'shadowsocks':
if (!ServerShadowsocks::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
case 'v2ray':
if (!ServerV2ray::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
case 'trojan':
if (!ServerTrojan::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
break;
}
}
DB::commit();

View File

@ -1,72 +0,0 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class RouteController extends Controller
{
public function fetch(Request $request)
{
$routes = ServerRoute::get();
// TODO: remove on 1.8.0
foreach ($routes as $k => $route) {
$array = json_decode($route->match, true);
if (is_array($array)) $routes[$k]['match'] = $array;
}
// TODO: remove on 1.8.0
return [
'data' => $routes
];
}
public function save(Request $request)
{
$params = $request->validate([
'remarks' => 'required',
'match' => 'required|array',
'action' => 'required|in:block,dns',
'action_value' => 'nullable'
], [
'remarks.required' => '备注不能为空',
'match.required' => '匹配值不能为空',
'action.required' => '动作类型不能为空',
'action.in' => '动作类型参数有误'
]);
$params['match'] = array_filter($params['match']);
// TODO: remove on 1.8.0
$params['match'] = json_encode($params['match']);
// TODO: remove on 1.8.0
if ($request->input('id')) {
try {
$route = ServerRoute::find($request->input('id'));
$route->update($params);
return [
'data' => true
];
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
if (!ServerRoute::create($params)) abort(500, '创建失败');
return [
'data' => true
];
}
public function drop(Request $request)
{
$route = ServerRoute::find($request->input('id'));
if (!$route) abort(500, '路由不存在');
if (!$route->delete()) abort(500, '删除失败');
return [
'data' => true
];
}
}

View File

@ -2,21 +2,21 @@
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerVmessSave;
use App\Http\Requests\Admin\ServerVmessUpdate;
use App\Http\Requests\Admin\ServerV2raySave;
use App\Http\Requests\Admin\ServerV2rayUpdate;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
class VmessController extends Controller
class V2rayController extends Controller
{
public function save(ServerVmessSave $request)
public function save(ServerV2raySave $request)
{
$params = $request->validated();
if ($request->input('id')) {
$server = ServerVmess::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
@ -30,7 +30,7 @@ class VmessController extends Controller
]);
}
if (!ServerVmess::create($params)) {
if (!ServerV2ray::create($params)) {
abort(500, '创建失败');
}
@ -42,7 +42,7 @@ class VmessController extends Controller
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerVmess::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
@ -52,13 +52,13 @@ class VmessController extends Controller
]);
}
public function update(ServerVmessUpdate $request)
public function update(ServerV2rayUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerVmess::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
@ -76,12 +76,12 @@ class VmessController extends Controller
public function copy(Request $request)
{
$server = ServerVmess::find($request->input('id'));
$server = ServerV2ray::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerVmess::create($server->toArray())) {
if (!ServerV2ray::create($server->toArray())) {
abort(500, '复制失败');
}

View File

@ -2,99 +2,28 @@
namespace App\Http\Controllers\Admin;
use App\Models\CommissionLog;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\StatUser;
use App\Services\ServerService;
use App\Services\StatisticalService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use App\Models\Stat;
use App\Models\StatOrder;
use App\Models\StatServer;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
public function getStat(Request $request)
{
$params = $request->validate([
'start_at' => '',
'end_at' => ''
]);
if (isset($params['start_at']) && isset($params['end_at'])) {
$stats = Stat::where('record_at', '>=', $params['start_at'])
->where('record_at', '<', $params['end_at'])
->get()
->makeHidden(['record_at', 'created_at', 'updated_at', 'id', 'record_type'])
->toArray();
} else {
$statisticalService = new StatisticalService();
return [
'data' => $statisticalService->generateStatData()
];
}
$stats = array_reduce($stats, function($carry, $item) {
foreach($item as $key => $value) {
if(isset($carry[$key]) && $carry[$key]) {
$carry[$key] += $value;
} else {
$carry[$key] = $value;
}
}
return $carry;
}, []);
return [
'data' => $stats
];
}
public function getStatRecord(Request $request)
{
$request->validate([
'type' => 'required|in:paid_total,commission_total,register_count',
'start_at' => '',
'end_at' => ''
]);
$statisticalService = new StatisticalService();
$statisticalService->setStartAt($request->input('start_at'));
$statisticalService->setEndAt($request->input('end_at'));
return [
'data' => $statisticalService->getStatRecord($request->input('type'))
];
}
public function getRanking(Request $request)
{
$request->validate([
'type' => 'required|in:server_traffic_rank,user_consumption_rank,invite_rank',
'start_at' => '',
'end_at' => '',
'limit' => 'nullable|integer'
]);
$statisticalService = new StatisticalService();
$statisticalService->setStartAt($request->input('start_at'));
$statisticalService->setEndAt($request->input('end_at'));
return [
'data' => $statisticalService->getRanking($request->input('type'), $request->input('limit') ?? 20)
];
}
public function getOverride(Request $request)
{
return [
return response([
'data' => [
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
@ -118,19 +47,21 @@ class StatController extends Controller
->where('created_at', '<', strtotime(date('Y-m-1')))
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
'commission_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->sum('get_amount'),
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->sum('actual_commission_balance'),
'commission_last_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('get_amount'),
->sum('actual_commission_balance'),
]
];
]);
}
public function getOrder(Request $request)
{
$statistics = Stat::where('record_type', 'd')
$statistics = StatOrder::where('record_type', 'd')
->limit(31)
->orderBy('record_at', 'DESC')
->get()
@ -138,50 +69,49 @@ class StatController extends Controller
$result = [];
foreach ($statistics as $statistic) {
$date = date('m-d', $statistic['record_at']);
$result[] = [
array_push($result, [
'type' => '收款金额',
'date' => $date,
'value' => $statistic['paid_total'] / 100
];
$result[] = [
'value' => $statistic['order_amount'] / 100
]);
array_push($result, [
'type' => '收款笔数',
'date' => $date,
'value' => $statistic['paid_count']
];
$result[] = [
'value' => $statistic['order_count']
]);
array_push($result, [
'type' => '佣金金额(已发放)',
'date' => $date,
'value' => $statistic['commission_total'] / 100
];
$result[] = [
'value' => $statistic['commission_amount'] / 100
]);
array_push($result, [
'type' => '佣金笔数(已发放)',
'date' => $date,
'value' => $statistic['commission_count']
];
]);
}
$result = array_reverse($result);
return [
return response([
'data' => $result
];
]);
}
public function getServerLastRank()
{
$servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'v2ray' => ServerVmess::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray(),
'vmess' => ServerVmess::where('parent_id', null)->get()->toArray()
'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
];
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$endAt = strtotime(date('Y-m-d'));
$statistics = StatServer::select([
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
->where('record_at', '>=', $startAt)
->where('record_at', '<', $endAt)
->where('record_type', 'd')
@ -198,9 +128,9 @@ class StatController extends Controller
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
}
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return [
return response([
'data' => $statistics
];
]);
}
public function getStatUser(Request $request)
@ -220,6 +150,5 @@ class StatController extends Controller
'total' => $total
];
}
}

View File

@ -2,9 +2,20 @@
namespace App\Http\Controllers\Admin;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
use App\Models\ServerV2ray;
use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use App\Models\StatOrder;
use App\Models\StatServer;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
@ -22,8 +33,7 @@ class SystemController extends Controller
return response([
'data' => [
'schedule' => $this->getScheduleStatus(),
'horizon' => $this->getHorizonStatus(),
'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null))
'horizon' => $this->getHorizonStatus()
]
]);
}

View File

@ -25,9 +25,9 @@ class ThemeController extends Controller
{
$themeConfigs = [];
foreach ($this->themes as $theme) {
$themeConfigFile = $this->path . "{$theme}/config.json";
$themeConfigFile = $this->path . "{$theme}/config.php";
if (!File::exists($themeConfigFile)) continue;
$themeConfig = json_decode(File::get($themeConfigFile), true);
$themeConfig = include($themeConfigFile);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
$themeConfigs[$theme] = $themeConfig;
if (config("theme.{$theme}")) continue;
@ -60,10 +60,9 @@ class ThemeController extends Controller
]);
$payload['config'] = json_decode(base64_decode($payload['config']), true);
if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误');
$themeConfigFile = public_path("theme/{$payload['name']}/config.json");
$themeConfigFile = public_path("theme/{$payload['name']}/config.php");
if (!File::exists($themeConfigFile)) abort(500, '主题不存在');
$themeConfig = json_decode(File::get($themeConfigFile), true);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, '主题配置文件有误');
$themeConfig = include($themeConfigFile);
$validateFields = array_column($themeConfig['configs'], 'field_name');
$config = [];
foreach ($validateFields as $validateField) {

View File

@ -7,7 +7,6 @@ use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Services\AuthService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
@ -129,11 +128,6 @@ class UserController extends Controller
$params['invite_user_id'] = null;
}
if (isset($params['banned']) && (int)$params['banned'] === 1) {
$authService = new AuthService($user);
$authService->removeAllSession();
}
try {
$user->update($params);
} catch (\Exception $e) {

View File

@ -5,7 +5,9 @@ namespace App\Http\Controllers\Client;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\Clash;
use Illuminate\Http\Request;
use App\Models\ServerV2ray;
use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml;
@ -31,18 +33,11 @@ class AppController extends Controller
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -2,10 +2,9 @@
namespace App\Http\Controllers\Client;
use App\Http\Controllers\Client\Protocols\General;
use App\Http\Controllers\Client\Protocols\V2rayN;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Services\UserService;
@ -14,7 +13,9 @@ class ClientController extends Controller
public function subscribe(Request $request)
{
$flag = $request->input('flag')
?? ($_SERVER['HTTP_USER_AGENT'] ?? '');
?? (isset($_SERVER['HTTP_USER_AGENT'])
? $_SERVER['HTTP_USER_AGENT']
: '');
$flag = strtolower($flag);
$user = $request->user;
// account not expired and is not banned.
@ -24,7 +25,7 @@ class ClientController extends Controller
$servers = $serverService->getAvailableServers($user);
$this->setSubscribeInfoToServers($servers, $user);
if ($flag) {
foreach (array_reverse(glob(app_path('Http//Controllers//Client//Protocols') . '/*.php')) as $file) {
foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
$file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
$class = new $file($user, $servers);
if (strpos($flag, $class->flag) !== false) {
@ -32,18 +33,19 @@ class ClientController extends Controller
}
}
}
$class = new General($user, $servers);
// todo 1.5.3 remove
$class = new V2rayN($user, $servers);
die($class->handle());
die('该客户端暂不支持进行订阅');
}
}
private function setSubscribeInfoToServers(&$servers, $user)
{
if (!isset($servers[0])) return;
if (!(int)config('v2board.show_info_to_server_enable', 0)) return;
$useTraffic = $user['u'] + $user['d'];
$totalTraffic = $user['transfer_enable'];
$remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic);
$useTraffic = round($user['u'] / (1024*1024*1024), 2) + round($user['d'] / (1024*1024*1024), 2);
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$remainingTraffic = $totalTraffic - $useTraffic;
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : '长期有效';
$userService = new UserService();
$resetDay = $userService->getResetDay($user);
@ -56,7 +58,7 @@ class ClientController extends Controller
]));
}
array_unshift($servers, array_merge($servers[0], [
'name' => "剩余流量:{$remainingTraffic}",
'name' => "剩余流量:{$remainingTraffic} GB",
]));
}
}

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Dict;
use phpDocumentor\Reflection\Types\Self_;
use Symfony\Component\Yaml\Yaml;
class Clash
@ -38,18 +36,11 @@ class Clash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
@ -77,18 +68,12 @@ class Clash
if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
return $group['proxies'];
});
$config['proxy-groups'] = array_values($config['proxy-groups']);
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST'];
if ($subsDomain) {
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
}
$subsDomain = $_SERVER['SERVER_NAME'];
$subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], $subsDomainRule);
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
@ -128,11 +113,6 @@ class Clash
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {

View File

@ -1,186 +0,0 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
use Symfony\Component\Yaml\Yaml;
class ClashMeta
{
public $flag = 'meta';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$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('profile-update-interval: 24');
header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName));
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
if (\File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'vmess') {
array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, self::buildTrojan($user['uuid'], $item));
array_push($proxies, $item['name']);
}
}
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
foreach ($config['proxy-groups'] as $k => $v) {
if (!is_array($config['proxy-groups'][$k]['proxies'])) $config['proxy-groups'][$k]['proxies'] = [];
$isFilter = false;
foreach ($config['proxy-groups'][$k]['proxies'] as $src) {
foreach ($proxies as $dst) {
if (!$this->isRegex($src)) continue;
$isFilter = true;
$config['proxy-groups'][$k]['proxies'] = array_values(array_diff($config['proxy-groups'][$k]['proxies'], [$src]));
if ($this->isMatch($src, $dst)) {
array_push($config['proxy-groups'][$k]['proxies'], $dst);
}
}
if ($isFilter) continue;
}
if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
return $group['proxies'];
});
$config['proxy-groups'] = array_values($config['proxy-groups']);
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST'];
if ($subsDomain) {
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
}
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}";
}
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'ss';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['cipher'] = $server['cipher'];
$array['password'] = $password;
$array['udp'] = true;
return $array;
}
public static function buildVmess($uuid, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'vmess';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['uuid'] = $uuid;
$array['alterId'] = 0;
$array['cipher'] = 'auto';
$array['udp'] = true;
if ($server['tls']) {
$array['tls'] = true;
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
$array['skip-cert-verify'] = ($tlsSettings['allowInsecure'] ? true : false);
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {
$wsSettings = $server['networkSettings'];
$array['ws-opts'] = [];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$array['ws-opts']['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$array['ws-opts']['headers'] = ['Host' => $wsSettings['headers']['Host']];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
$array['ws-path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
$array['ws-headers'] = ['Host' => $wsSettings['headers']['Host']];
}
}
if ($server['network'] === 'grpc') {
$array['network'] = 'grpc';
if ($server['networkSettings']) {
$grpcSettings = $server['networkSettings'];
$array['grpc-opts'] = [];
if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
}
}
return $array;
}
public static function buildTrojan($password, $server)
{
$array = [];
$array['name'] = $server['name'];
$array['type'] = 'trojan';
$array['server'] = $server['host'];
$array['port'] = $server['port'];
$array['password'] = $password;
$array['udp'] = true;
if (!empty($server['server_name'])) $array['sni'] = $server['server_name'];
if (!empty($server['allow_insecure'])) $array['skip-cert-verify'] = ($server['allow_insecure'] ? true : false);
return $array;
}
private function isMatch($exp, $str)
{
return @preg_match($exp, $str);
}
private function isRegex($exp)
{
return @preg_match($exp, null) !== false;
}
}

View File

@ -1,113 +0,0 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class General
{
public $flag = 'general';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return base64_encode($uri);
}
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}";
}
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server['cipher']}:{$password}")
);
return "ss://{$str}@{$server['host']}:{$server['port']}#{$name}\r\n";
}
public static function buildVmess($uuid, $server)
{
$config = [
"v" => "2",
"ps" => $server['name'],
"add" => $server['host'],
"port" => (string)$server['port'],
"id" => $uuid,
"aid" => '0',
"net" => $server['network'],
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server['tls'] ? "tls" : "",
];
if ($server['tls']) {
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
if (isset($wsSettings['headers']['Host'])) $config['host'] = $wsSettings['headers']['Host'];
}
if ((string)$server['network'] === 'grpc') {
$grpcSettings = $server['networkSettings'];
if (isset($grpcSettings['serviceName'])) $config['path'] = $grpcSettings['serviceName'];
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan($password, $server)
{
$name = rawurlencode($server['name']);
$query = http_build_query([
'allowInsecure' => $server['allow_insecure'],
'peer' => $server['server_name'],
'sni' => $server['server_name']
]);
$uri = "trojan://{$password}@{$server['host']}:{$server['port']}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -1,137 +0,0 @@
<?php
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class Loon
{
public $flag = 'loon';
private $servers;
private $user;
public function __construct($user, $servers)
{
$this->user = $user;
$this->servers = $servers;
}
public function handle()
{
$servers = $this->servers;
$user = $this->user;
$uri = '';
header("Subscription-Userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'vmess') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
$uri .= self::buildTrojan($user['uuid'], $item);
}
}
return $uri;
}
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server['name']}=Shadowsocks",
"{$server['host']}",
"{$server['port']}",
"{$server['cipher']}",
"{$password}",
'fast-open=false',
'udp=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"{$server['name']}=vmess",
"{$server['host']}",
"{$server['port']}",
'auto',
"{$uuid}",
'fast-open=false',
'udp=true',
"alterId=0"
];
if ($server['network'] === 'tcp') {
array_push($config, 'transport=tcp');
if ($server['networkSettings']) {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type']) && !empty($tcpSettings['header']['type']))
$config = str_replace('transport=tcp', "transport={$tcpSettings['header']['type']}", $config);
if (isset($tcpSettings['header']['request']['path'][0]) && !empty($tcpSettings['header']['request']['path'][0]))
array_push($config, "path={$tcpSettings['header']['request']['path'][0]}");
if (isset($tcpSettings['header']['Host']) && !empty($tcpSettings['header']['Host']))
array_push($config, "host={$tcpSettings['header']['Host']}");
}
}
if ($server['tls']) {
if ($server['network'] === 'tcp')
array_push($config, 'over-tls=true');
if ($server['tlsSettings']) {
$tlsSettings = $server['tlsSettings'];
if (isset($tlsSettings['allowInsecure']) && !empty($tlsSettings['allowInsecure']))
array_push($config, 'skip-cert-verify=' . ($tlsSettings['allowInsecure'] ? 'true' : 'false'));
if (isset($tlsSettings['serverName']) && !empty($tlsSettings['serverName']))
array_push($config, "tls-name={$tlsSettings['serverName']}");
}
}
if ($server['network'] === 'ws') {
array_push($config, 'transport=ws');
if ($server['networkSettings']) {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path']) && !empty($wsSettings['path']))
array_push($config, "path={$wsSettings['path']}");
if (isset($wsSettings['headers']['Host']) && !empty($wsSettings['headers']['Host']))
array_push($config, "host={$wsSettings['headers']['Host']}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildTrojan($password, $server)
{
$config = [
"{$server['name']}=trojan",
"{$server['host']}",
"{$server['port']}",
"{$password}",
$server['server_name'] ? "tls-name={$server['server_name']}" : "",
'fast-open=false',
'udp=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

@ -22,7 +22,7 @@ class Passwall
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
@ -68,11 +68,6 @@ class Passwall
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -25,7 +25,7 @@ class QuantumultX
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {

View File

@ -22,7 +22,7 @@ class SSRPlus
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {

View File

@ -21,7 +21,7 @@ class SagerNet
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
@ -72,11 +72,6 @@ class SagerNet
$config['sni'] = urlencode($tlsSettings['serverName']);
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class Shadowrocket
{
public $flag = 'shadowrocket';
@ -32,7 +30,7 @@ class Shadowrocket
if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item);
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'trojan') {
@ -45,16 +43,6 @@ class Shadowrocket
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}";
}
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
@ -82,15 +70,6 @@ class Shadowrocket
$config['peer'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
if ($server['networkSettings']) {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type']) && !empty($tcpSettings['header']['type']))
$config['obfs'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0]) && !empty($tcpSettings['header']['request']['path'][0]))
$config['path'] = $tcpSettings['header']['request']['path'][0];
}
}
if ($server['network'] === 'ws') {
$config['obfs'] = "websocket";
if ($server['networkSettings']) {

View File

@ -29,9 +29,7 @@ class Shadowsocks
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], ['aes-128-gcm', 'aes-256-gcm', 'aes-192-gcm', 'chacha20-ietf-poly1305'])
) {
if ($item['type'] === 'shadowsocks') {
array_push($configs, self::SIP008($item, $user));
}
}

View File

@ -36,18 +36,11 @@ class Stash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
if ($item['type'] === 'shadowsocks') {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
@ -75,17 +68,12 @@ class Stash
if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
return $group['proxies'];
});
$config['proxy-groups'] = array_values($config['proxy-groups']);
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST'];
if ($subsDomain) {
array_unshift($config['rules'], "DOMAIN,{$subsDomain},DIRECT");
}
$subsDomain = $_SERVER['SERVER_NAME'];
$subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], $subsDomainRule);
$yaml = Yaml::dump($config, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE);
$yaml = Yaml::dump($config);
$yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml;
}
@ -125,11 +113,6 @@ class Stash
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {

View File

@ -28,20 +28,13 @@ class Surfboard
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
@ -65,21 +58,12 @@ class Surfboard
// Subscription link
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
$subsDomain = $_SERVER['HTTP_HOST'];
$subsDomain = $_SERVER['SERVER_NAME'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
$upload = round($user['u'] / (1024*1024*1024), 2);
$download = round($user['d'] / (1024*1024*1024), 2);
$useTraffic = $upload + $download;
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量{$download}GB\\n剩余流量{$useTraffic}GB\\n套餐流量{$totalTraffic}GB\\n到期时间{$expireDate}";
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
return $config;
}

View File

@ -28,20 +28,13 @@ class Surge
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
if ($item['type'] === 'shadowsocks') {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]
$proxyGroup .= $item['name'] . ', ';
}
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
// [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group]
@ -65,22 +58,13 @@ class Surge
// Subscription link
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
$subsDomain = $_SERVER['HTTP_HOST'];
$subsDomain = $_SERVER['SERVER_NAME'];
$subsURL = 'https://' . $subsDomain . '/api/v1/client/subscribe?token=' . $user['token'];
$config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
$upload = round($user['u'] / (1024*1024*1024), 2);
$download = round($user['d'] / (1024*1024*1024), 2);
$useTraffic = $upload + $download;
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$subscribeInfo = "title={$appName}订阅信息, content=上传流量:{$upload}GB\\n下载流量{$download}GB\\n剩余流量{$useTraffic}GB\\n套餐流量{$totalTraffic}GB\\n到期时间{$expireDate}";
$config = str_replace('$subscribe_info', $subscribeInfo, $config);
return $config;
}

View File

@ -3,8 +3,6 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class V2rayN
{
public $flag = 'v2rayn';
@ -24,7 +22,7 @@ class V2rayN
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
@ -39,16 +37,6 @@ class V2rayN
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}";
}
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],
@ -80,11 +68,6 @@ class V2rayN
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -22,7 +22,7 @@ class V2rayNG
$uri = '';
foreach ($servers as $item) {
if ($item['type'] === 'vmess') {
if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item);
}
if ($item['type'] === 'shadowsocks') {
@ -68,11 +68,6 @@ class V2rayNG
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -7,7 +7,6 @@ use App\Http\Requests\Passport\AuthRegister;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use App\Jobs\SendEmailJob;
use App\Services\AuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Plan;
@ -26,7 +25,7 @@ class AuthController extends Controller
abort(404);
}
$params = $request->validate([
'email' => 'required|email:strict',
'email' => 'required|email',
'redirect' => 'nullable'
]);
@ -78,9 +77,7 @@ class AuthController extends Controller
if ((int)config('v2board.register_limit_by_ip_enable', 0)) {
$registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0;
if ((int)$registerCountByIP >= (int)config('v2board.register_limit_count', 3)) {
abort(500, __('Register frequently, please try again after :minute minute', [
'minute' => config('v2board.register_limit_expire', 60)
]));
abort(500, __('Register frequently, please try again after 1 hour'));
}
}
if ((int)config('v2board.recaptcha_enable', 0)) {
@ -116,7 +113,7 @@ class AuthController extends Controller
if (empty($request->input('email_code'))) {
abort(500, __('Email verification code cannot be empty'));
}
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, __('Incorrect email verification code'));
}
}
@ -156,7 +153,6 @@ class AuthController extends Controller
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
$user->speed_limit = $plan->speed_limit;
}
}
@ -167,6 +163,11 @@ class AuthController extends Controller
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$user->last_login_at = time();
$user->save();
@ -177,11 +178,8 @@ class AuthController extends Controller
(int)config('v2board.register_limit_expire', 60) * 60
);
}
$authService = new AuthService($user);
return response()->json([
'data' => $authService->generateAuthData($request)
'data' => $data
]);
}
@ -190,15 +188,6 @@ class AuthController extends Controller
$email = $request->input('email');
$password = $request->input('password');
if ((int)config('v2board.password_limit_enable', 1)) {
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
if ($passwordErrorCount >= (int)config('v2board.password_limit_count', 5)) {
abort(500, __('There are too many password errors, please try again after :minute minutes.', [
'minute' => config('v2board.password_limit_expire', 60)
]));
}
}
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, __('Incorrect email or password'));
@ -209,13 +198,6 @@ class AuthController extends Controller
$password,
$user->password)
) {
if ((int)config('v2board.password_limit_enable')) {
Cache::put(
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
(int)$passwordErrorCount + 1,
60 * (int)config('v2board.password_limit_expire', 60)
);
}
abort(500, __('Incorrect email or password'));
}
@ -223,9 +205,14 @@ class AuthController extends Controller
abort(500, __('Your account has been suspended'));
}
$authService = new AuthService($user);
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
if ($user->is_admin) $data['is_admin'] = true;
return response([
'data' => $authService->generateAuthData($request)
'data' => $data
]);
}
@ -254,25 +241,49 @@ class AuthController extends Controller
if ($user->banned) {
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
Cache::forget($key);
$authService = new AuthService($user);
return response([
'data' => $authService->generateAuthData($request)
'data' => $data
]);
}
}
public function getTempToken(Request $request)
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
return response([
'data' => $code
]);
}
public function getQuickLoginUrl(Request $request)
{
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!isset($authData[0]) || !isset($authData[1])) abort(403, __('Token error'));
$user = User::where('email', $authData[0])
->where('password', $authData[1])
->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user['id'], 60);
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;
@ -286,7 +297,7 @@ class AuthController extends Controller
public function forget(AuthForget $request)
{
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
abort(500, __('Incorrect email verification code'));
}
$user = User::where('email', $request->input('email'))->first();

View File

@ -3,13 +3,12 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
@ -38,11 +37,11 @@ class DeepbworkController extends Controller
{
ini_set('memory_limit', -1);
$nodeId = $request->input('node_id');
$server = ServerVmess::find($nodeId);
$server = ServerV2ray::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $server->id), time(), 3600);
Cache::put(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
@ -70,7 +69,7 @@ class DeepbworkController extends Controller
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = ServerVmess::find($request->input('node_id'));
$server = ServerV2ray::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
@ -79,15 +78,14 @@ class DeepbworkController extends Controller
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_VMESS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_VMESS_LAST_PUSH_AT', $server->id), time(), 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);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'vmess');
}
$userService->trafficFetch($server->toArray(), 'vmess', $formatData);
return response([
'ret' => 1,
@ -114,7 +112,7 @@ class DeepbworkController extends Controller
private function getV2RayConfig(int $nodeId, int $localPort)
{
$server = ServerVmess::find($nodeId);
$server = ServerV2ray::find($nodeId);
if (!$server) {
abort(500, '节点不存在');
}
@ -131,7 +129,7 @@ class DeepbworkController extends Controller
return $json;
}
private function setDns(ServerVmess $server, object $json)
private function setDns(ServerV2ray $server, object $json)
{
if ($server->dnsSettings) {
$dns = $server->dnsSettings;
@ -144,7 +142,7 @@ class DeepbworkController extends Controller
}
}
private function setNetwork(ServerVmess $server, object $json)
private function setNetwork(ServerV2ray $server, object $json)
{
if ($server->networkSettings) {
switch ($server->network) {
@ -173,7 +171,7 @@ class DeepbworkController extends Controller
}
}
private function setRule(ServerVmess $server, object $json)
private function setRule(ServerV2ray $server, object $json)
{
$domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain')));
$protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol')));
@ -213,7 +211,7 @@ class DeepbworkController extends Controller
}
}
private function setTls(ServerVMess $server, object $json)
private function setTls(ServerV2ray $server, object $json)
{
if ((int)$server->tls) {
$tlsSettings = $server->tlsSettings;

View File

@ -4,7 +4,6 @@ namespace App\Http\Controllers\Server;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
@ -74,12 +73,11 @@ class ShadowsocksTidalabController extends Controller
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);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'shadowsocks');
}
$userService->trafficFetch($server->toArray(), 'shadowsocks', $formatData);
return response([
'ret' => 1,

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
@ -79,11 +78,11 @@ class TrojanTidalabController extends Controller
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);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'trojan');
}
$userService->trafficFetch($server->toArray(), 'trojan', $formatData);
return response([
'ret' => 1,

View File

@ -3,23 +3,21 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\ServerTrojan;
use Illuminate\Support\Facades\Cache;
class UniProxyController extends Controller
class VProxyController extends Controller
{
private $nodeType;
private $nodeInfo;
private $nodeId;
private $serverService;
private $token;
public function __construct(Request $request)
{
@ -30,12 +28,25 @@ class UniProxyController extends Controller
if ($token !== config('v2board.server_token')) {
abort(500, 'token is error');
}
$this->token = $token;
$this->nodeType = $request->input('node_type');
if ($this->nodeType === 'v2ray') $this->nodeType = 'vmess';
$this->nodeId = $request->input('node_id');
$this->serverService = new ServerService();
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
if (!$this->nodeInfo) abort(500, 'server is not exist');
switch ($this->nodeType) {
case 'v2ray':
$this->nodeInfo = ServerV2ray::find($this->nodeId);
break;
case 'shadowsocks':
$this->nodeInfo = ServerShadowsocks::find($this->nodeId);
break;
case 'trojan':
$this->nodeInfo = ServerTrojan::find($this->nodeId);
break;
default:
break;
}
if (!$this->nodeInfo) {
abort(500, 'server not found');
}
}
// 后端获取用户
@ -43,11 +54,21 @@ class UniProxyController extends Controller
{
ini_set('memory_limit', -1);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600);
$users = $this->serverService->getAvailableUsers($this->nodeInfo->group_id);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($this->nodeInfo->group_id);
$users = $users->toArray();
$response['users'] = $users;
switch ($this->nodeType) {
case 'shadowsocks':
$response['server'] = [
'cipher' => $this->nodeInfo->cipher,
'server_port' => $this->nodeInfo->server_port
];
break;
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
@ -57,14 +78,18 @@ class UniProxyController extends Controller
}
// 后端提交数据
public function push(Request $request)
public function submit(Request $request)
{
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
$userService = new UserService();
$userService->trafficFetch($this->nodeInfo->toArray(), $this->nodeType, $data);
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo->toArray(), $this->nodeType);
}
return response([
'data' => true
@ -76,58 +101,28 @@ class UniProxyController extends Controller
{
switch ($this->nodeType) {
case 'shadowsocks':
$response = [
die(json_encode([
'server_port' => $this->nodeInfo->server_port,
'cipher' => $this->nodeInfo->cipher,
'obfs' => $this->nodeInfo->obfs,
'obfs_settings' => $this->nodeInfo->obfs_settings
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 32);
}
], JSON_UNESCAPED_UNICODE));
break;
case 'vmess':
$response = [
case 'v2ray':
die(json_encode([
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'cipher' => $this->nodeInfo->cipher,
'networkSettings' => $this->nodeInfo->networkSettings,
'tls' => $this->nodeInfo->tls
];
], JSON_UNESCAPED_UNICODE));
break;
case 'trojan':
$response = [
die(json_encode([
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
];
break;
case 'hysteria':
$response = [
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
'up_mbps' => $this->nodeInfo->up_mbps,
'down_mbps' => $this->nodeInfo->down_mbps,
'obfs' => Helper::getServerKey($this->nodeInfo->created_at, 16)
];
'server_port' => $this->nodeInfo->server_port
], JSON_UNESCAPED_UNICODE));
break;
}
$response['base_config'] = [
'push_interval' => (int)config('v2board.server_push_interval', 60),
'pull_interval' => (int)config('v2board.server_pull_interval', 60)
];
if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response($response)->header('ETag', "\"{$eTag}\"");
}
}

View File

@ -58,21 +58,19 @@ class InviteController extends Controller
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
}
$uncheck_commission_balance = (int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance');
if (config('v2board.commission_distribution_enable', 0)) {
$uncheck_commission_balance = $uncheck_commission_balance * (config('v2board.commission_distribution_l1') / 100);
}
$stat = [
//已注册用户数
(int)User::where('invite_user_id', $request->user['id'])->count(),
//有效的佣金
(int)CommissionLog::where('invite_user_id', $request->user['id'])
->sum('get_amount'),
(int)Order::where('status', 3)
->where('commission_status', 2)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
//确认中的佣金
$uncheck_commission_balance,
(int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
//佣金比例
(int)$commission_rate,
//可用佣金

View File

@ -85,7 +85,7 @@ class OrderController extends Controller
abort(500, __('Subscription plan does not exist'));
}
if ($user->plan_id !== $plan->id && !$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
if (!$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
abort(500, __('Current product is sold out'));
}
@ -191,7 +191,6 @@ class OrderController extends Controller
$payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
}

View File

@ -8,7 +8,7 @@ use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\ServerLog;
use App\Models\User;
@ -26,13 +26,8 @@ class ServerController extends Controller
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response([
'data' => $servers
])->header('ETag', "\"{$eTag}\"");
]);
}
}

View File

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword;
use App\Services\AuthService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
@ -19,30 +18,6 @@ use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
public function getActiveSession(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$authService = new AuthService($user);
return response([
'data' => $authService->getSessions()
]);
}
public function removeActiveSession(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$authService = new AuthService($user);
return response([
'data' => $authService->removeSession($request->input('session_id'))
]);
}
public function checkLogin(Request $request)
{
$data = [
@ -138,8 +113,7 @@ class UserController extends Controller
'u',
'd',
'transfer_enable',
'email',
'uuid'
'email'
])
->first();
if (!$user) {

View File

@ -70,7 +70,6 @@ class Kernel extends HttpKernel
'admin' => \App\Http\Middleware\Admin::class,
'client' => \App\Http\Middleware\Client::class,
'staff' => \App\Http\Middleware\Staff::class,
'log' => \App\Http\Middleware\RequestLog::class
];
/**

View File

@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
@ -20,10 +19,24 @@ class Admin
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$user = AuthService::decryptAuthData($authorization);
if (!$user || !$user['is_admin']) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
if (!$user->is_admin) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$request->merge([
'user' => $user
'user' => Cache::get($authorization)
]);
return $next($request);
}

View File

@ -1,24 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
class RequestLog
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->method() === 'POST') {
$path = $request->path();
info("POST {$path}");
};
return $next($request);
}
}

View File

@ -2,8 +2,8 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
class Staff
{
@ -19,10 +19,24 @@ class Staff
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$user = AuthService::decryptAuthData($authorization);
if (!$user || !$user['is_staff']) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
if (!$user->is_staff) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$request->merge([
'user' => $user
'user' => Cache::get($authorization)
]);
return $next($request);
}

View File

@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
@ -20,10 +19,23 @@ class User
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$request->merge([
'user' => $user
'user' => Cache::get($authorization)
]);
return $next($request);
}

View File

@ -24,7 +24,9 @@ class ConfigSave extends FormRequest
// site
'logo' => 'nullable|url',
'force_https' => 'in:0,1',
'safe_mode_enable' => 'in:0,1',
'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '',
'app_description' => '',
'app_url' => 'nullable|url',
@ -32,9 +34,18 @@ class ConfigSave extends FormRequest
'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric',
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => 'nullable|array',
'email_gmail_limit_enable' => 'in:0,1',
'recaptcha_enable' => 'in:0,1',
'recaptcha_key' => '',
'recaptcha_site_key' => '',
'tos_url' => 'nullable|url',
'currency' => '',
'currency_symbol' => '',
'register_limit_by_ip_enable' => 'in:0,1',
'register_limit_count' => 'integer',
'register_limit_expire' => 'integer',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1,2,3,4',
@ -45,14 +56,17 @@ class ConfigSave extends FormRequest
'show_info_to_server_enable' => 'in:0,1',
// server
'server_token' => 'nullable|min:16',
'server_pull_interval' => 'integer',
'server_push_interval' => 'integer',
'server_license' => 'nullable',
'server_log_enable' => 'in:0,1',
'server_v2ray_domain' => '',
'server_v2ray_protocol' => '',
// frontend
'frontend_theme' => '',
'frontend_theme_sidebar' => 'nullable|in:dark,light',
'frontend_theme_header' => 'nullable|in:dark,light',
'frontend_theme_color' => 'nullable|in:default,darkblue,black,green',
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black,green',
'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '',
// email
'email_template' => '',
'email_host' => '',
@ -73,23 +87,7 @@ class ConfigSave extends FormRequest
'macos_version' => '',
'macos_download_url' => '',
'android_version' => '',
'android_download_url' => '',
// safe
'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => 'nullable|array',
'email_gmail_limit_enable' => 'in:0,1',
'recaptcha_enable' => 'in:0,1',
'recaptcha_key' => '',
'recaptcha_site_key' => '',
'email_verify' => 'in:0,1',
'safe_mode_enable' => 'in:0,1',
'register_limit_by_ip_enable' => 'in:0,1',
'register_limit_count' => 'integer',
'register_limit_expire' => 'integer',
'secure_path' => 'min:8|regex:/^[\w-]*$/',
'password_limit_enable' => 'in:0,1',
'password_limit_count' => 'integer',
'password_limit_expire' => 'integer',
'android_download_url' => ''
];
/**
* Get the validation rules that apply to the request.
@ -110,9 +108,7 @@ class ConfigSave extends FormRequest
'server_token.min' => '通讯密钥长度必须大于16位',
'tos_url.url' => '服务条款URL格式不正确必须携带http(s)://',
'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式必须携带http(s)://',
'logo.url' => 'LOGO URL格式不正确必须携带https(s)://',
'secure_path.min' => '后台路径长度最小为8位',
'secure_path.regex' => '后台路径只能为字母或数字'
'logo.url' => 'LOGO URL格式不正确必须携带https(s)://'
];
}
}

View File

@ -14,7 +14,7 @@ class OrderFetch extends FormRequest
public function rules()
{
return [
'filter.*.key' => 'required|in:email,trade_no,status,commission_status,user_id,invite_user_id,callback_no,commission_balance',
'filter.*.key' => 'required|in:email,trade_no,status,commission_status,user_id,invite_user_id,callback_no',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => ''
];

View File

@ -27,8 +27,7 @@ class PlanSave extends FormRequest
'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer',
'reset_traffic_method' => 'nullable|integer|in:0,1,2,3,4',
'capacity_limit' => 'nullable|integer',
'speed_limit' => 'nullable|integer'
'capacity_limit' => 'nullable|integer'
];
}
@ -50,8 +49,7 @@ class PlanSave extends FormRequest
'reset_price.integer' => '流量重置包金额有误',
'reset_traffic_method.integer' => '流量重置方式格式有误',
'reset_traffic_method.in' => '流量重置方式格式有误',
'capacity_limit.integer' => '容纳用户量限制格式有误',
'speed_limit.integer' => '限速格式有误'
'capacity_limit.integer' => '容纳用户量限制格式有误'
];
}
}

View File

@ -18,11 +18,10 @@ class ServerShadowsocksSave extends FormRequest
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'route_id' => 'nullable|array',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'cipher' => 'required|in:aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm',
'cipher' => 'required|in:aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305',
'obfs' => 'nullable|in:http',
'obfs_settings' => 'nullable|array',
'tags' => 'nullable|array',
@ -36,7 +35,6 @@ class ServerShadowsocksSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -17,7 +17,6 @@ class ServerTrojanSave extends FormRequest
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
@ -35,7 +34,6 @@ class ServerTrojanSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerVmessSave extends FormRequest
class ServerV2raySave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
@ -17,7 +17,6 @@ class ServerVmessSave extends FormRequest
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
@ -39,7 +38,6 @@ class ServerVmessSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerVmessUpdate extends FormRequest
class ServerV2rayUpdate extends FormRequest
{
/**
* Get the validation rules that apply to the request.

View File

@ -14,7 +14,7 @@ class UserFetch extends FormRequest
public function rules()
{
return [
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks,is_admin',
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => 'required'
];

View File

@ -14,7 +14,7 @@ class UserUpdate extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict',
'email' => 'required|email',
'password' => 'nullable|min:8',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
@ -29,8 +29,7 @@ class UserUpdate extends FormRequest
'balance' => 'integer',
'commission_type' => 'integer',
'commission_balance' => 'integer',
'remarks' => 'nullable',
'speed_limit' => 'nullable|integer'
'remarks' => 'nullable'
];
}
@ -60,8 +59,7 @@ class UserUpdate extends FormRequest
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确',
'password.min' => '密码长度最小8位',
'speed_limit.integer' => '限速格式不正确'
'password.min' => '密码长度最小8位'
];
}
}

View File

@ -14,7 +14,7 @@ class AuthForget extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict',
'email' => 'required|email',
'password' => 'required|min:8',
'email_code' => 'required'
];

View File

@ -14,7 +14,7 @@ class AuthLogin extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict',
'email' => 'required|email',
'password' => 'required|min:8'
];
}

View File

@ -14,7 +14,7 @@ class AuthRegister extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict',
'email' => 'required|email',
'password' => 'required|min:8'
];
}

View File

@ -14,7 +14,7 @@ class CommSendEmailVerify extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict'
'email' => 'required|email'
];
}

View File

@ -14,7 +14,7 @@ class UserUpdate extends FormRequest
public function rules()
{
return [
'email' => 'required|email:strict',
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',

View File

@ -8,8 +8,8 @@ class AdminRoute
public function map(Registrar $router)
{
$router->group([
'prefix' => config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key')))),
'middleware' => ['admin', 'log']
'prefix' => 'admin',
'middleware' => 'admin'
], function ($router) {
// Config
$router->get ('/config/fetch', 'Admin\\ConfigController@fetch');
@ -28,9 +28,6 @@ class AdminRoute
$router->get ('/server/group/fetch', 'Admin\\Server\\GroupController@fetch');
$router->post('/server/group/save', 'Admin\\Server\\GroupController@save');
$router->post('/server/group/drop', 'Admin\\Server\\GroupController@drop');
$router->get ('/server/route/fetch', 'Admin\\Server\\RouteController@fetch');
$router->post('/server/route/save', 'Admin\\Server\\RouteController@save');
$router->post('/server/route/drop', 'Admin\\Server\\RouteController@drop');
$router->get ('/server/manage/getNodes', 'Admin\\Server\\ManageController@getNodes');
$router->post('/server/manage/sort', 'Admin\\Server\\ManageController@sort');
$router->group([
@ -45,14 +42,14 @@ class AdminRoute
$router->post('viewConfig', 'Admin\\Server\\TrojanController@viewConfig');
});
$router->group([
'prefix' => 'server/vmess'
'prefix' => 'server/v2ray'
], function ($router) {
$router->get ('fetch', 'Admin\\Server\\VmessController@fetch');
$router->post('save', 'Admin\\Server\\VmessController@save');
$router->post('drop', 'Admin\\Server\\VmessController@drop');
$router->post('update', 'Admin\\Server\\VmessController@update');
$router->post('copy', 'Admin\\Server\\VmessController@copy');
$router->post('sort', 'Admin\\Server\\VmessController@sort');
$router->get ('fetch', 'Admin\\Server\\V2rayController@fetch');
$router->post('save', 'Admin\\Server\\V2rayController@save');
$router->post('drop', 'Admin\\Server\\V2rayController@drop');
$router->post('update', 'Admin\\Server\\V2rayController@update');
$router->post('copy', 'Admin\\Server\\V2rayController@copy');
$router->post('sort', 'Admin\\Server\\V2rayController@sort');
});
$router->group([
'prefix' => 'server/shadowsocks'
@ -64,16 +61,6 @@ class AdminRoute
$router->post('copy', 'Admin\\Server\\ShadowsocksController@copy');
$router->post('sort', 'Admin\\Server\\ShadowsocksController@sort');
});
$router->group([
'prefix' => 'server/hysteria'
], function ($router) {
$router->get ('fetch', 'Admin\\Server\\HysteriaController@fetch');
$router->post('save', 'Admin\\Server\\HysteriaController@save');
$router->post('drop', 'Admin\\Server\\HysteriaController@drop');
$router->post('update', 'Admin\\Server\\HysteriaController@update');
$router->post('copy', 'Admin\\Server\\HysteriaController@copy');
$router->post('sort', 'Admin\\Server\\HysteriaController@sort');
});
// Order
$router->get ('/order/fetch', 'Admin\\OrderController@fetch');
$router->post('/order/update', 'Admin\\OrderController@update');
@ -91,14 +78,11 @@ class AdminRoute
$router->post('/user/ban', 'Admin\\UserController@ban');
$router->post('/user/resetSecret', 'Admin\\UserController@resetSecret');
$router->post('/user/setInviteUser', 'Admin\\UserController@setInviteUser');
// Stat
$router->get ('/stat/getStat', 'Admin\\StatController@getStat');
// StatOrder
$router->get ('/stat/getOverride', 'Admin\\StatController@getOverride');
$router->get ('/stat/getServerLastRank', 'Admin\\StatController@getServerLastRank');
$router->get ('/stat/getOrder', 'Admin\\StatController@getOrder');
$router->get ('/stat/getStatUser', 'Admin\\StatController@getStatUser');
$router->get ('/stat/getRanking', 'Admin\\StatController@getRanking');
$router->get ('/stat/getStatRecord', 'Admin\\StatController@getStatRecord');
// Notice
$router->get ('/notice/fetch', 'Admin\\NoticeController@fetch');
$router->post('/notice/save', 'Admin\\NoticeController@save');
@ -134,7 +118,6 @@ class AdminRoute
$router->get ('/system/getQueueStats', 'Admin\\SystemController@getQueueStats');
$router->get ('/system/getQueueWorkload', 'Admin\\SystemController@getQueueWorkload');
$router->get ('/system/getQueueMasters', '\\Laravel\\Horizon\\Http\\Controllers\\MasterSupervisorController@index');
$router->get ('/system/getSystemLog', 'Admin\\SystemController@getSystemLog');
// Theme
$router->get ('/theme/getThemes', 'Admin\\ThemeController@getThemes');
$router->post('/theme/saveThemeConfig', 'Admin\\ThemeController@saveThemeConfig');

View File

@ -14,6 +14,7 @@ class ClientRoute
// Client
$router->get('/subscribe', 'Client\\ClientController@subscribe');
// App
$router->get('/app/config', 'Client\\AppController@config');
$router->get('/app/getConfig', 'Client\\AppController@getConfig');
$router->get('/app/getVersion', 'Client\\AppController@getVersion');
});

View File

@ -21,12 +21,11 @@ class UserRoute
$router->get ('/checkLogin', 'User\\UserController@checkLogin');
$router->post('/transfer', 'User\\UserController@transfer');
$router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
$router->get ('/getActiveSession', 'User\\UserController@getActiveSession');
$router->post('/removeActiveSession', 'User\\UserController@removeActiveSession');
// Order
$router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout');
$router->get ('/order/check', 'User\\OrderController@check');
$router->get ('/order/details', 'User\\OrderController@detail'); // TODO: 1.7.0 remove
$router->get ('/order/detail', 'User\\OrderController@detail');
$router->get ('/order/fetch', 'User\\OrderController@fetch');
$router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');

View File

@ -15,8 +15,6 @@ class OrderHandleJob implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $order;
public $tries = 3;
public $timeout = 5;
/**
* Create a new job instance.
*

View File

@ -16,8 +16,6 @@ class SendEmailJob implements ShouldQueue
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $params;
public $tries = 3;
public $timeout = 10;
/**
* Create a new job instance.
*

View File

@ -16,7 +16,7 @@ class SendTelegramJob implements ShouldQueue
protected $text;
public $tries = 3;
public $timeout = 10;
public $timeout = 5;
/**
* Create a new job instance.

View File

@ -0,0 +1,78 @@
<?php
namespace App\Jobs;
use App\Models\StatServer;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatServerJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $server;
protected $protocol;
protected $recordType;
public $tries = 3;
public $timeout = 60;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $server, $protocol, $recordType = 'd')
{
$this->onQueue('stat');
$this->u = $u;
$this->d = $d;
$this->server = $server;
$this->protocol = $protocol;
$this->recordType = $recordType;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$recordAt = strtotime(date('Y-m-d'));
if ($this->recordType === 'm') {
//
}
$data = StatServer::lockForUpdate()
->where('record_at', $recordAt)
->where('server_id', $this->server['id'])
->where('server_type', $this->protocol)
->first();
if ($data) {
try {
$data->update([
'u' => $data['u'] + $this->u,
'd' => $data['d'] + $this->d
]);
} catch (\Exception $e) {
abort(500, '节点统计数据更新失败');
}
} else {
if (!StatServer::create([
'server_id' => $this->server['id'],
'server_type' => $this->protocol,
'u' => $this->u,
'd' => $this->d,
'record_type' => $this->recordType,
'record_at' => $recordAt
])) {
abort(500, '节点统计数据创建失败');
}
}
}
}

80
app/Jobs/StatUserJob.php Normal file
View File

@ -0,0 +1,80 @@
<?php
namespace App\Jobs;
use App\Models\StatServer;
use App\Models\StatUser;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class StatUserJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $u;
protected $d;
protected $userId;
protected $server;
protected $protocol;
protected $recordType;
public $tries = 3;
public $timeout = 60;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct($u, $d, $userId, array $server, $protocol, $recordType = 'd')
{
$this->onQueue('stat');
$this->u = $u;
$this->d = $d;
$this->userId = $userId;
$this->server = $server;
$this->protocol = $protocol;
$this->recordType = $recordType;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$recordAt = strtotime(date('Y-m-d'));
if ($this->recordType === 'm') {
//
}
$data = StatUser::where('record_at', $recordAt)
->where('server_rate', $this->server['rate'])
->where('user_id', $this->userId)
->first();
if ($data) {
try {
$data->update([
'u' => $data['u'] + ($this->u * $this->server['rate']),
'd' => $data['d'] + ($this->d * $this->server['rate'])
]);
} catch (\Exception $e) {
abort(500, '用户统计数据更新失败');
}
} else {
if (!StatUser::create([
'user_id' => $this->userId,
'server_rate' => $this->server['rate'],
'u' => $this->u,
'd' => $this->d,
'record_type' => $this->recordType,
'record_at' => $recordAt
])) {
abort(500, '用户统计数据创建失败');
}
}
}
}

View File

@ -20,7 +20,7 @@ class TrafficFetchJob implements ShouldQueue
protected $protocol;
public $tries = 3;
public $timeout = 10;
public $timeout = 3;
/**
* Create a new job instance.
@ -50,8 +50,8 @@ class TrafficFetchJob implements ShouldQueue
$user->t = time();
$user->u = $user->u + ($this->u * $this->server['rate']);
$user->d = $user->d + ($this->d * $this->server['rate']);
if (!$user->save()) {
info("流量更新失败\n未记录用户ID:{$this->userId}\n未记录上行:{$user->u}\n未记录下行:{$user->d}");
}
if (!$user->save()) throw new \Exception('流量更新失败');
$mailService = new MailService();
$mailService->remindTraffic($user);
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace App\Logging;
class MysqlLogger
{
public function __invoke(array $config){
return tap(new \Monolog\Logger('mysql'), function ($logger) {
$logger->pushHandler(new MysqlLoggerHandler());
});
}
}

View File

@ -1,44 +0,0 @@
<?php
namespace App\Logging;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use App\Models\Log as LogModel;
class MysqlLoggerHandler extends AbstractProcessingHandler
{
public function __construct($level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
}
protected function write(array $record): void
{
try{
if(isset($record['context']['exception']) && is_object($record['context']['exception'])){
$record['context']['exception'] = (array)$record['context']['exception'];
}
$record['request_data'] = request()->all() ??[];
$log = [
'title' => $record['message'],
'level' => $record['level_name'],
'host' => $record['request_host'] ?? request()->getSchemeAndHttpHost(),
'uri' => $record['request_uri'] ?? request()->getRequestUri(),
'method' => $record['request_method'] ?? request()->getMethod(),
'ip' => request()->getClientIp(),
'data' => json_encode($record['request_data']) ,
'context' => isset($record['context']) ? json_encode($record['context']) : '',
'created_at' => strtotime($record['datetime']),
'updated_at' => strtotime($record['datetime']),
];
LogModel::insert(
$log
);
}catch (\Exception $e){
Log::channel('daily')->error($e->getMessage().$e->getFile().$e->getTraceAsString());
}
}
}

View File

@ -1,19 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerHysteria extends Model
{
protected $table = 'v2_server_hysteria';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tags' => 'array'
];
}

View File

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

View File

@ -13,7 +13,6 @@ class ServerShadowsocks extends Model
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tags' => 'array',
'obfs_settings' => 'array'
];

View File

@ -13,7 +13,6 @@ class ServerTrojan extends Model
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tags' => 'array'
];
}

View File

@ -4,16 +4,15 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerVmess extends Model
class ServerV2ray extends Model
{
protected $table = 'v2_server_vmess';
protected $table = 'v2_server_v2ray';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tlsSettings' => 'array',
'networkSettings' => 'array',
'dnsSettings' => 'array',

View File

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

View File

@ -4,9 +4,9 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Log extends Model
class StatOrder extends Model
{
protected $table = 'v2_log';
protected $table = 'v2_stat_order';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [

View File

@ -28,11 +28,6 @@ class AlipayF2F {
'label' => '支付宝公钥',
'description' => '',
'type' => 'input',
],
'product_name' => [
'label' => '自定义商品名称',
'description' => '将会体现在支付宝账单中',
'type' => 'input'
]
];
}
@ -47,7 +42,7 @@ class AlipayF2F {
$gateway->setAlipayPublicKey($this->config['public_key']); // 可以是路径,也可以是密钥内容
$gateway->setNotifyUrl($order['notify_url']);
$gateway->setBizContent([
'subject' => $this->config['product_name'] ?? (config('v2board.app_name', 'V2Board') . ' - 订阅'),
'subject' => config('v2board.app_name', 'V2Board') . ' - 订阅',
'out_trade_no' => $order['trade_no'],
'total_amount' => $order['total_amount'] / 100
]);

View File

@ -32,11 +32,6 @@ class MGate {
'label' => 'AppSecret',
'description' => '',
'type' => 'input',
],
'mgate_source_currency' => [
'label' => '源货币',
'description' => '默认CNY',
'type' => 'input'
]
];
}
@ -49,9 +44,6 @@ class MGate {
'notify_url' => $order['notify_url'],
'return_url' => $order['return_url']
];
if (isset($this->config['mgate_source_currency'])) {
$params['source_currency'] = $this->config['mgate_source_currency'];
}
$params['app_id'] = $this->config['mgate_app_id'];
ksort($params);
$str = http_build_query($params) . $this->config['mgate_app_secret'];

View File

@ -33,11 +33,6 @@ class StripeCheckout {
'label' => 'WebHook 密钥签名',
'description' => '',
'type' => 'input',
],
'stripe_custom_field_name' => [
'label' => '自定义字段名称',
'description' => '例如可设置为“联系方式”,以便及时与客户取得联系',
'type' => 'input',
]
];
}
@ -49,7 +44,6 @@ class StripeCheckout {
if (!$exchange) {
abort(500, __('Currency conversion has timed out, please try again later'));
}
$customFieldName = isset($this->config['stripe_custom_field_name']) ? $this->config['stripe_custom_field_name'] : 'Contact Infomation';
$params = [
'success_url' => $order['return_url'],
@ -67,16 +61,7 @@ class StripeCheckout {
'quantity' => 1
]
],
'mode' => 'payment',
'invoice_creation' => ['enabled' => true],
'phone_number_collection' => ['enabled' => true],
'custom_fields' => [
[
'key' => 'contactinfo',
'label' => ['type' => 'custom', 'custom' => $customFieldName],
'type' => 'text',
],
],
'mode' => 'payment'
// 'customer_email' => $user['email'] not support
];

View File

@ -1,104 +0,0 @@
<?php
namespace App\Services;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
class AuthService
{
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function generateAuthData(Request $request)
{
$guid = Helper::guid();
$authData = JWT::encode([
'id' => $this->user->id,
'session' => $guid,
], config('app.key'), 'HS256');
self::addSession($this->user->id, $guid, [
'ip' => $request->ip(),
'login_at' => time(),
'ua' => $request->userAgent()
]);
return [
'token' => $this->user->token,
'is_admin' => $this->user->is_admin,
'auth_data' => $authData
];
}
public static function decryptAuthData($jwt)
{
try {
if (!Cache::has($jwt)) {
$data = (array)JWT::decode($jwt, new Key(config('app.key'), 'HS256'));
if (!self::checkSession($data['id'], $data['session'])) return false;
$user = User::select([
'id',
'email',
'is_admin',
'is_staff'
])
->find($data['id']);
if (!$user) return false;
Cache::put($jwt, $user->toArray(), 3600);
}
return Cache::get($jwt);
} catch (\Exception $e) {
return false;
}
}
private static function checkSession($userId, $session)
{
$sessions = (array)Cache::get(CacheKey::get("USER_SESSIONS", $userId)) ?? [];
if (!in_array($session, array_keys($sessions))) return false;
return true;
}
private static function addSession($userId, $guid, $meta)
{
$cacheKey = CacheKey::get("USER_SESSIONS", $userId);
$sessions = (array)Cache::get($cacheKey, []);
$sessions[$guid] = $meta;
if (!Cache::put(
$cacheKey,
$sessions
)) return false;
return true;
}
public function getSessions()
{
return (array)Cache::get(CacheKey::get("USER_SESSIONS", $this->user->id), []);
}
public function removeSession($sessionId)
{
$cacheKey = CacheKey::get("USER_SESSIONS", $this->user->id);
$sessions = (array)Cache::get($cacheKey, []);
unset($sessions[$sessionId]);
if (!Cache::put(
$cacheKey,
$sessions
)) return false;
return true;
}
public function removeAllSession()
{
$cacheKey = CacheKey::get("USER_SESSIONS", $this->user->id);
return Cache::forget($cacheKey);
}
}

View File

@ -71,8 +71,6 @@ class OrderService
break;
}
$this->setSpeedLimit($plan->speed_limit);
if (!$this->user->save()) {
DB::rollBack();
abort(500, '开通失败');
@ -184,35 +182,43 @@ class OrderService
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id');
}
private function orderIsUsed(Order $order):bool
{
$month = self::STR_TO_TIME[$order->period];
$orderExpireDay = strtotime('+' . $month . ' month', $order->created_at);
if ($orderExpireDay < time()) return true;
return false;
}
private function getSurplusValueByPeriod(User $user, Order $order)
{
$orders = Order::where('user_id', $user->id)
$orderModel = Order::where('user_id', $user->id)
->where('period', '!=', 'reset_price')
->where('period', '!=', 'onetime_price')
->where('status', 3)
->get()
->toArray();
if (!$orders) return;
$orderAmountSum = 0;
$orderMonthSum = 0;
$lastValidateAt = 0;
foreach ($orders as $item) {
$period = self::STR_TO_TIME[$item['period']];
if (strtotime("+{$period} month", $item['created_at']) < time()) continue;
$lastValidateAt = $item['created_at'];
$orderMonthSum = $period + $orderMonthSum;
$orderAmountSum = $orderAmountSum + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']);
->where('status', 3);
$orders = $orderModel->get();
$orderSurplusMonth = 0;
$orderSurplusAmount = 0;
$userSurplusMonth = ($user->expired_at - time()) / 2678400;
foreach ($orders as $k => $item) {
// 兼容历史余留问题
if ($item->period === 'onetime_price') continue;
if ($this->orderIsUsed($item)) continue;
$orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->period];
$orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']);
}
if (!$orderSurplusMonth || !$orderSurplusAmount) return;
$monthUnitPrice = $orderSurplusAmount / $orderSurplusMonth;
// 如果用户过期月大于订单过期月
if ($userSurplusMonth > $orderSurplusMonth) {
$orderSurplusAmount = $orderSurplusMonth * $monthUnitPrice;
} else {
$orderSurplusAmount = $userSurplusMonth * $monthUnitPrice;
}
if (!$orderSurplusAmount) {
return;
}
if (!$lastValidateAt) return;
$expiredAtByOrder = strtotime("+{$orderMonthSum} month", $lastValidateAt);
if ($expiredAtByOrder < time()) return;
$orderSurplusSecond = $expiredAtByOrder - time();
$orderRangeSecond = $expiredAtByOrder - $lastValidateAt;
$avgPrice = $orderAmountSum / $orderRangeSecond;
$orderSurplusAmount = $avgPrice * $orderSurplusSecond;
if (!$orderSurplusSecond || !$orderSurplusAmount) return;
$order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
$order->surplus_order_ids = array_column($orders, 'id');
$order->surplus_order_ids = array_column($orders->toArray(), 'id');
}
public function paid(string $callbackNo)
@ -223,11 +229,7 @@ class OrderService
$order->paid_at = time();
$order->callback_no = $callbackNo;
if (!$order->save()) return false;
try {
OrderHandleJob::dispatchNow($order->trade_no);
} catch (\Exception $e) {
return false;
}
OrderHandleJob::dispatch($order->trade_no);
return true;
}
@ -251,11 +253,6 @@ class OrderService
return true;
}
private function setSpeedLimit($speedLimit)
{
$this->user->speed_limit = $speedLimit;
}
private function buyByResetTraffic()
{
$this->user->u = 0;

View File

@ -2,12 +2,10 @@
namespace App\Services;
use App\Models\ServerHysteria;
use App\Models\ServerLog;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
use App\Models\User;
use App\Models\ServerVmess;
use App\Models\ServerV2ray;
use App\Models\ServerTrojan;
use App\Utils\CacheKey;
use App\Utils\Helper;
@ -16,115 +14,96 @@ use Illuminate\Support\Facades\Cache;
class ServerService
{
public function getAvailableVmess(User $user):array
public function getV2ray(User $user, $all = false):array
{
$servers = [];
$model = ServerVmess::orderBy('sort', 'ASC');
$vmess = $model->get();
foreach ($vmess as $key => $v) {
if (!$v['show']) continue;
$vmess[$key]['type'] = 'vmess';
if (!in_array($user->group_id, $vmess[$key]['group_id'])) continue;
if (strpos($vmess[$key]['port'], '-') !== false) {
$vmess[$key]['port'] = Helper::randomPort($vmess[$key]['port']);
$model = ServerV2ray::orderBy('sort', 'ASC');
if (!$all) {
$model->where('show', 1);
}
$v2ray = $model->get();
for ($i = 0; $i < count($v2ray); $i++) {
$v2ray[$i]['type'] = 'v2ray';
$groupId = $v2ray[$i]['group_id'];
if (!in_array($user->group_id, $groupId)) continue;
if (strpos($v2ray[$i]['port'], '-') !== false) {
$v2ray[$i]['port'] = Helper::randomPort($v2ray[$i]['port']);
}
if ($vmess[$key]['parent_id']) {
$vmess[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $vmess[$key]['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']));
} else {
$vmess[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $vmess[$key]['id']));
$v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['id']));
}
$servers[] = $vmess[$key]->toArray();
array_push($servers, $v2ray[$i]->toArray());
}
return $servers;
}
public function getAvailableTrojan(User $user):array
public function getTrojan(User $user, $all = false):array
{
$servers = [];
$model = ServerTrojan::orderBy('sort', 'ASC');
if (!$all) {
$model->where('show', 1);
}
$trojan = $model->get();
foreach ($trojan as $key => $v) {
if (!$v['show']) continue;
$trojan[$key]['type'] = 'trojan';
if (!in_array($user->group_id, $trojan[$key]['group_id'])) continue;
if (strpos($trojan[$key]['port'], '-') !== false) {
$trojan[$key]['port'] = Helper::randomPort($trojan[$key]['port']);
for ($i = 0; $i < count($trojan); $i++) {
$trojan[$i]['type'] = 'trojan';
$groupId = $trojan[$i]['group_id'];
if (!in_array($user->group_id, $groupId)) continue;
if (strpos($trojan[$i]['port'], '-') !== false) {
$trojan[$i]['port'] = Helper::randomPort($trojan[$i]['port']);
}
if ($trojan[$key]['parent_id']) {
$trojan[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$key]['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']));
} else {
$trojan[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$key]['id']));
$trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['id']));
}
$servers[] = $trojan[$key]->toArray();
array_push($servers, $trojan[$i]->toArray());
}
return $servers;
}
public function getAvailableHysteria(User $user)
{
$availableServers = [];
$model = ServerHysteria::orderBy('sort', 'ASC');
$servers = $model->get()->keyBy('id');
foreach ($servers as $key => $v) {
if (!$v['show']) continue;
$servers[$key]['type'] = 'hysteria';
$servers[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_HYSTERIA_LAST_CHECK_AT', $v['id']));
if (!in_array($user->group_id, $v['group_id'])) continue;
if (strpos($v['port'], '-') !== false) {
$servers[$key]['port'] = Helper::randomPort($v['port']);
}
if (isset($servers[$v['parent_id']])) {
$servers[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_HYSTERIA_LAST_CHECK_AT', $v['parent_id']));
$servers[$key]['created_at'] = $servers[$v['parent_id']]['created_at'];
}
$servers[$key]['server_key'] = Helper::getServerKey($servers[$key]['created_at'], 16);
$availableServers[] = $servers[$key]->toArray();
}
return $availableServers;
}
public function getAvailableShadowsocks(User $user)
public function getShadowsocks(User $user, $all = false)
{
$servers = [];
$model = ServerShadowsocks::orderBy('sort', 'ASC');
$shadowsocks = $model->get()->keyBy('id');
foreach ($shadowsocks as $key => $v) {
if (!$v['show']) continue;
$shadowsocks[$key]['type'] = 'shadowsocks';
$shadowsocks[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $v['id']));
if (!in_array($user->group_id, $v['group_id'])) continue;
if (strpos($v['port'], '-') !== false) {
$shadowsocks[$key]['port'] = Helper::randomPort($v['port']);
if (!$all) {
$model->where('show', 1);
}
$shadowsocks = $model->get();
for ($i = 0; $i < count($shadowsocks); $i++) {
$shadowsocks[$i]['type'] = 'shadowsocks';
$groupId = $shadowsocks[$i]['group_id'];
if (!in_array($user->group_id, $groupId)) continue;
if (strpos($shadowsocks[$i]['port'], '-') !== false) {
$shadowsocks[$i]['port'] = Helper::randomPort($shadowsocks[$i]['port']);
}
if (isset($shadowsocks[$v['parent_id']])) {
$shadowsocks[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $v['parent_id']));
$shadowsocks[$key]['created_at'] = $shadowsocks[$v['parent_id']]['created_at'];
if ($shadowsocks[$i]['parent_id']) {
$shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id']));
} else {
$shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['id']));
}
$servers[] = $shadowsocks[$key]->toArray();
array_push($servers, $shadowsocks[$i]->toArray());
}
return $servers;
}
public function getAvailableServers(User $user)
public function getAvailableServers(User $user, $all = false)
{
$servers = array_merge(
$this->getAvailableShadowsocks($user),
$this->getAvailableVmess($user),
$this->getAvailableTrojan($user),
$this->getAvailableHysteria($user)
$this->getShadowsocks($user, $all),
$this->getV2ray($user, $all),
$this->getTrojan($user, $all)
);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
return array_map(function ($server) {
$server['port'] = (int)$server['port'];
$server['is_online'] = (time() - 300 > $server['last_check_at']) ? 0 : 1;
$server['cache_key'] = "{$server['type']}-{$server['id']}-{$server['updated_at']}-{$server['is_online']}";
return $server;
}, $servers);
return $servers;
}
public function getAvailableUsers($groupId)
{
return User::whereIn('group_id', $groupId)
@ -136,8 +115,7 @@ class ServerService
->where('banned', 0)
->select([
'id',
'uuid',
'speed_limit'
'uuid'
])
->get();
}
@ -174,57 +152,45 @@ class ServerService
}
}
public function getAllShadowsocks()
public function getShadowsocksServers()
{
$servers = ServerShadowsocks::orderBy('sort', 'ASC')
->get()
->toArray();
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'shadowsocks';
$server = ServerShadowsocks::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'shadowsocks';
}
return $servers;
return $server->toArray();
}
public function getAllVMess()
public function getV2rayServers()
{
$servers = ServerVmess::orderBy('sort', 'ASC')
->get()
->toArray();
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'vmess';
$server = ServerV2ray::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'v2ray';
}
return $servers;
return $server->toArray();
}
public function getAllTrojan()
public function getTrojanServers()
{
$servers = ServerTrojan::orderBy('sort', 'ASC')
->get()
->toArray();
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'trojan';
$server = ServerTrojan::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) {
$server[$i]['type'] = 'trojan';
}
return $servers;
return $server->toArray();
}
public function getAllHysteria()
{
$servers = ServerHysteria::orderBy('sort', 'ASC')
->get()
->toArray();
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'hysteria';
}
return $servers;
}
private function mergeData(&$servers)
public function mergeData(&$servers)
{
foreach ($servers as $k => $v) {
$serverType = strtoupper($v['type']);
$servers[$k]['online'] = Cache::get(CacheKey::get("SERVER_{$serverType}_ONLINE_USER", $v['parent_id'] ?? $v['id']));
$servers[$k]['last_check_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_CHECK_AT", $v['parent_id'] ?? $v['id']));
$servers[$k]['last_push_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_PUSH_AT", $v['parent_id'] ?? $v['id']));
$serverType = strtoupper($servers[$k]['type']);
$servers[$k]['online'] = Cache::get(CacheKey::get("SERVER_{$serverType}_ONLINE_USER", $servers[$k]['parent_id'] ? $servers[$k]['parent_id'] : $servers[$k]['id']));
if ($servers[$k]['parent_id']) {
$servers[$k]['last_check_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_CHECK_AT", $servers[$k]['parent_id']));
$servers[$k]['last_push_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_PUSH_AT", $servers[$k]['parent_id']));
} else {
$servers[$k]['last_check_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_CHECK_AT", $servers[$k]['id']));
$servers[$k]['last_push_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_PUSH_AT", $servers[$k]['id']));
}
if ((time() - 300) >= $servers[$k]['last_check_at']) {
$servers[$k]['available_status'] = 0;
} else if ((time() - 300) >= $servers[$k]['last_push_at']) {
@ -238,42 +204,13 @@ class ServerService
public function getAllServers()
{
$servers = array_merge(
$this->getAllShadowsocks(),
$this->getAllVMess(),
$this->getAllTrojan(),
$this->getAllHysteria()
$this->getShadowsocksServers(),
$this->getV2rayServers(),
$this->getTrojanServers()
);
$this->mergeData($servers);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
return $servers;
}
public function getRoutes(array $routeIds)
{
$routes = ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get();
// TODO: remove on 1.8.0
foreach ($routes as $k => $route) {
$array = json_decode($route->match, true);
if (is_array($array)) $routes[$k]['match'] = $array;
}
// TODO: remove on 1.8.0
return $routes;
}
public function getServer($serverId, $serverType)
{
switch ($serverType) {
case 'vmess':
return ServerVmess::find($serverId);
case 'shadowsocks':
return ServerShadowsocks::find($serverId);
case 'trojan':
return ServerTrojan::find($serverId);
case 'hysteria':
return ServerHysteria::find($serverId);
default:
return false;
}
}
}

View File

@ -1,283 +0,0 @@
<?php
namespace App\Services;
use App\Models\CommissionLog;
use App\Models\Order;
use App\Models\Stat;
use App\Models\StatServer;
use App\Models\StatUser;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class StatisticalService {
protected $userStats;
protected $startAt;
protected $endAt;
protected $serverStats;
public function __construct()
{
ini_set('memory_limit', -1);
}
public function setStartAt($timestamp) {
$this->startAt = $timestamp;
}
public function setEndAt($timestamp) {
$this->endAt = $timestamp;
}
public function setServerStats() {
$this->serverStats = Cache::get("stat_server_{$this->startAt}");
$this->serverStats = json_decode($this->serverStats, true) ?? [];
if (!is_array($this->serverStats)) {
$this->serverStats = [];
}
}
public function setUserStats() {
$this->userStats = Cache::get("stat_user_{$this->startAt}");
$this->userStats = json_decode($this->userStats, true) ?? [];
if (!is_array($this->userStats)) {
$this->userStats = [];
}
}
public function generateStatData(): array
{
$startAt = $this->startAt;
$endAt = $this->endAt;
if (!$startAt || !$endAt) {
$startAt = strtotime(date('Y-m-d'));
$endAt = strtotime('+1 day', $startAt);
}
$data = [];
$data['order_count'] = Order::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->count();
$data['order_total'] = Order::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->sum('total_amount');
$data['paid_count'] = Order::where('paid_at', '>=', $startAt)
->where('paid_at', '<', $endAt)
->whereNotIn('status', [0, 2])
->count();
$data['paid_total'] = Order::where('paid_at', '>=', $startAt)
->where('paid_at', '<', $endAt)
->whereNotIn('status', [0, 2])
->sum('total_amount');
$commissionLogBuilder = CommissionLog::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt);
$data['commission_count'] = $commissionLogBuilder->count();
$data['commission_total'] = $commissionLogBuilder->sum('get_amount');
$data['register_count'] = User::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->count();
$data['invite_count'] = User::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->whereNotNull('invite_user_id')
->count();
$data['transfer_used_total'] = StatServer::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt)
->select(DB::raw('SUM(u) + SUM(d) as total'))
->value('total') ?? 0;
return $data;
}
public function statServer($serverId, $serverType, $u, $d)
{
$this->serverStats[$serverType] = $this->serverStats[$serverType] ?? [];
if (isset($this->serverStats[$serverType][$serverId])) {
$this->serverStats[$serverType][$serverId][0] += $u;
$this->serverStats[$serverType][$serverId][1] += $d;
} else {
$this->serverStats[$serverType][$serverId] = [$u, $d];
}
Cache::set("stat_server_{$this->startAt}", json_encode($this->serverStats));
}
public function statUser($rate, $userId, $u, $d)
{
$this->userStats[$rate] = $this->userStats[$rate] ?? [];
if (isset($this->userStats[$rate][$userId])) {
$this->userStats[$rate][$userId][0] += $u;
$this->userStats[$rate][$userId][1] += $d;
} else {
$this->userStats[$rate][$userId] = [$u, $d];
}
Cache::set("stat_user_{$this->startAt}", json_encode($this->userStats));
}
public function getStatUserByUserID($userId): array
{
$stats = [];
foreach (array_keys($this->userStats) as $rate) {
if (!isset($this->userStats[$rate][$userId])) continue;
$stats[] = [
'record_at' => $this->startAt,
'server_rate' => $rate,
'u' => $this->userStats[$rate][$userId][0],
'd' => $this->userStats[$rate][$userId][1],
'user_id' => $userId
];
}
return $stats;
}
public function getStatUser()
{
$stats = [];
foreach ($this->userStats as $k => $v) {
foreach (array_keys($v) as $userId) {
if (isset($v[$userId])) {
$stats[] = [
'server_rate' => $k,
'u' => $v[$userId][0],
'd' => $v[$userId][1],
'user_id' => $userId
];
}
}
}
return $stats;
}
public function getStatServer()
{
$stats = [];
foreach ($this->serverStats as $serverType => $v) {
foreach (array_keys($v) as $serverId) {
if (isset($v[$serverId])) {
$stats[] = [
'server_id' => $serverId,
'server_type' => $serverType,
'u' => $v[$serverId][0],
'd' => $v[$serverId][1],
];
}
}
}
return $stats;
}
public function clearStatUser()
{
Cache::forget("stat_user_{$this->startAt}");
}
public function clearStatServer()
{
Cache::forget("stat_server_{$this->startAt}");
}
public function getStatRecord($type)
{
switch ($type) {
case "paid_total": {
return Stat::select([
'*',
DB::raw('paid_total / 100 as paid_total')
])
->where('record_at', '>=', $this->startAt)
->where('record_at', '<', $this->endAt)
->orderBy('record_at', 'ASC')
->get();
}
case "commission_total": {
return Stat::select([
'*',
DB::raw('commission_total / 100 as commission_total')
])
->where('record_at', '>=', $this->startAt)
->where('record_at', '<', $this->endAt)
->orderBy('record_at', 'ASC')
->get();
}
case "register_count": {
return Stat::where('record_at', '>=', $this->startAt)
->where('record_at', '<', $this->endAt)
->orderBy('record_at', 'ASC')
->get();
}
}
}
public function getRanking($type, $limit = 20)
{
switch ($type) {
case 'server_traffic_rank': {
return $this->buildServerTrafficRank($limit);
}
case 'user_consumption_rank': {
return $this->buildUserConsumptionRank($limit);
}
case 'invite_rank': {
return $this->buildInviteRank($limit);
}
}
}
private function buildInviteRank($limit)
{
$stats = User::select([
'invite_user_id',
DB::raw('count(*) as count')
])
->where('created_at', '>=', $this->startAt)
->where('created_at', '<', $this->endAt)
->whereNotNull('invite_user_id')
->groupBy('invite_user_id')
->orderBy('count', 'DESC')
->limit($limit)
->get();
$users = User::whereIn('id', $stats->pluck('invite_user_id')->toArray())->get()->keyBy('id');
foreach ($stats as $k => $v) {
if (!isset($users[$v['invite_user_id']])) continue;
$stats[$k]['email'] = $users[$v['invite_user_id']]['email'];
}
return $stats;
}
private function buildUserConsumptionRank($limit)
{
$stats = StatUser::select([
'user_id',
DB::raw('sum(u) as u'),
DB::raw('sum(d) as d'),
DB::raw('sum(u) + sum(d) as total')
])
->where('record_at', '>=', $this->startAt)
->where('record_at', '<', $this->endAt)
->groupBy('user_id')
->orderBy('total', 'DESC')
->limit($limit)
->get();
$users = User::whereIn('id', $stats->pluck('user_id')->toArray())->get()->keyBy('id');
foreach ($stats as $k => $v) {
if (!isset($users[$v['user_id']])) continue;
$stats[$k]['email'] = $users[$v['user_id']]['email'];
}
return $stats;
}
private function buildServerTrafficRank($limit)
{
return StatServer::select([
'server_id',
'server_type',
DB::raw('sum(u) as u'),
DB::raw('sum(d) as d'),
DB::raw('sum(u) + sum(d) as total')
])
->where('record_at', '>=', $this->startAt)
->where('record_at', '<', $this->endAt)
->groupBy('server_id', 'server_type')
->orderBy('total', 'DESC')
->limit($limit)
->get();
}
}

View File

@ -18,10 +18,9 @@ class ThemeService
public function init()
{
$themeConfigFile = $this->path . "{$this->theme}/config.json";
if (!File::exists($themeConfigFile)) abort(500, "{$this->theme}主题不存在");
$themeConfig = json_decode(File::get($themeConfigFile), true);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, "{$this->theme}主题配置文件有误");
$themeConfigFile = $this->path . "{$this->theme}/config.php";
if (!File::exists($themeConfigFile)) return;
$themeConfig = include($themeConfigFile);
$configs = $themeConfig['configs'];
$data = [];
foreach ($configs as $config) {

View File

@ -2,12 +2,17 @@
namespace App\Services;
use App\Jobs\ServerLogJob;
use App\Jobs\StatServerJob;
use App\Jobs\StatUserJob;
use App\Jobs\TrafficFetchJob;
use App\Models\InviteCode;
use App\Models\Order;
use App\Models\Plan;
use App\Models\ServerV2ray;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class UserService
{
@ -28,9 +33,9 @@ class UserService
}
if ((int)$day >= (int)$today) {
return $day - $today;
} else {
return $lastDay - $today + $day;
}
return $lastDay - $today + $day;
}
private function calcResetDayByYearFirstDay(): int
@ -44,9 +49,6 @@ class UserService
$md = date('m-d', $expiredAt);
$nowYear = strtotime(date("Y-{$md}"));
$nextYear = strtotime('+1 year', $nowYear);
if ($nowYear > time()) {
return (int)(($nowYear - time()) / 86400);
}
return (int)(($nextYear - time()) / 86400);
}
@ -168,18 +170,10 @@ class UserService
return true;
}
public function trafficFetch(array $server, string $protocol, array $data)
public function trafficFetch(int $u, int $d, int $userId, array $server, string $protocol)
{
$statService = new StatisticalService();
$statService->setStartAt(strtotime(date('Y-m-d')));
$statService->setUserStats();
$statService->setServerStats();
foreach (array_keys($data) as $userId) {
$u = $data[$userId][0];
$d = $data[$userId][1];
TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
$statService->statServer($server['id'], $protocol, $u, $d);
$statService->statUser($server['rate'], $userId, $u, $d);
}
TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
StatServerJob::dispatch($u, $d, $server, $protocol, 'd');
StatUserJob::dispatch($u, $d, $userId, $server, $protocol, 'd');
}
}

View File

@ -7,25 +7,20 @@ class CacheKey
CONST KEYS = [
'EMAIL_VERIFY_CODE' => '邮箱验证码',
'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间',
'SERVER_VMESS_ONLINE_USER' => '节点在线用户',
'SERVER_VMESS_LAST_CHECK_AT' => '节点最后检查时间',
'SERVER_VMESS_LAST_PUSH_AT' => '节点最后推送时间',
'SERVER_V2RAY_ONLINE_USER' => '节点在线用户',
'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',
'SERVER_V2RAY_LAST_PUSH_AT' => '节点最后推送时间',
'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间',
'SERVER_TROJAN_LAST_PUSH_AT' => 'trojan节点最后推送时间',
'SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点在线用户',
'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间',
'SERVER_HYSTERIA_ONLINE_USER' => 'hysteria节点在线用户',
'SERVER_HYSTERIA_LAST_CHECK_AT' => 'hysteria节点最后检查时间',
'SERVER_HYSTERIA_LAST_PUSH_AT' => 'hysteria节点最后推送时间',
'TEMP_TOKEN' => '临时令牌',
'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',
'REGISTER_IP_RATE_LIMIT' => '注册频率限制',
'LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP' => '最后一次发送登入链接时间',
'PASSWORD_ERROR_LIMIT' => '密码错误次数限制',
'USER_SESSIONS' => '用户session'
'LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP' => '最后一次发送登入链接时间'
];
public static function get(string $key, $uniqueValue)

View File

@ -4,16 +4,6 @@ namespace App\Utils;
class Helper
{
public static function uuidToBase64($uuid, $length)
{
return base64_encode(substr($uuid, 0, $length));
}
public static function getServerKey($timestamp, $length)
{
return base64_encode(substr(md5($timestamp), 0, $length));
}
public static function guid($format = false)
{
if (function_exists('com_create_guid') === true) {
@ -30,8 +20,8 @@ class Helper
public static function generateOrderNo(): string
{
$randomChar = mt_rand(10000, 99999);
return date('YmdHms') . substr(microtime(), 2, 6) . $randomChar;
$randomChar = rand(10000, 99999);
return date('YmdHms') . $randomChar;
}
public static function exchange($from, $to)

View File

@ -13,7 +13,6 @@
"require": {
"php": "^7.3.0|^8.0",
"fideloper/proxy": "^4.4",
"firebase/php-jwt": "^6.3",
"fruitcake/laravel-cors": "^2.0",
"google/recaptcha": "^1.2",
"guzzlehttp/guzzle": "^7.4.3",

View File

@ -237,5 +237,5 @@ return [
| The only modification by laravel config
|
*/
'version' => '1.7.4.1681103823832'
'version' => '1.6.1.1665920414108'
];

View File

@ -175,6 +175,7 @@ return [
'queue' => [
'order_handle',
'traffic_fetch',
'stat',
'send_email',
'send_email_mass',
'send_telegram',
@ -183,7 +184,7 @@ return [
'minProcesses' => 1,
'maxProcesses' => (int)ceil($parser->getRam()['total'] / 1024 / 1024 / 1024 * 6),
'tries' => 1,
'balanceCooldown' => 3,
'nice' => 0,
],
],
],

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