Compare commits

..

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

114 changed files with 773 additions and 2133 deletions

View File

@ -14,7 +14,7 @@ DB_USERNAME=root
DB_PASSWORD=123456 DB_PASSWORD=123456
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_DRIVER=redis
QUEUE_CONNECTION=redis QUEUE_CONNECTION=redis
SESSION_DRIVER=redis SESSION_DRIVER=redis
SESSION_LIFETIME=120 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)) { if ((int)config('v2board.commission_auto_check_enable', 1)) {
Order::where('commission_status', 0) Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL) ->where('invite_user_id', '!=', NULL)
->where('status', 3) ->whereNotIn('status', [0, 2])
->where('updated_at', '<=', strtotime('-3 day', time())) ->where('updated_at', '<=', strtotime('-3 day', time()))
->update([ ->update([
'commission_status' => 1 'commission_status' => 1

View File

@ -2,7 +2,6 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Models\Log;
use App\Models\Plan; use App\Models\Plan;
use App\Models\StatServer; use App\Models\StatServer;
use App\Models\StatUser; use App\Models\StatUser;
@ -47,6 +46,5 @@ class ResetLog extends Command
{ {
StatUser::where('record_at', '<', strtotime('-2 month', time()))->delete(); StatUser::where('record_at', '<', strtotime('-2 month', time()))->delete();
StatServer::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();
} }
} }

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(); $mailService = new MailService();
foreach ($users as $user) { foreach ($users as $user) {
if ($user->remind_expire) $mailService->remindExpire($user); if ($user->remind_expire) $mailService->remindExpire($user);
if ($user->remind_traffic) $mailService->remindTraffic($user);
} }
} }
} }

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Utils\Helper;
use Illuminate\Console\Command; use Illuminate\Console\Command;
class Test extends Command class Test extends Command

View File

@ -48,8 +48,8 @@ class V2boardInstall extends Command
$this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | "); $this->info(" \ V / / __/| |_) | (_) | (_| | | | (_| | ");
$this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| "); $this->info(" \_/ |_____|____/ \___/ \__,_|_| \__,_| ");
if (\File::exists(base_path() . '/.env')) { if (\File::exists(base_path() . '/.env')) {
$securePath = config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key')))); $defaultSecurePath = hash('crc32b', config('app.key'));
$this->info("访问 http(s)://你的站点/{$securePath} 进入管理面板,你可以在用户中心修改你的密码。"); $this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以在用户中心修改你的密码。");
abort(500, '如需重新安装请删除目录下.env文件'); abort(500, '如需重新安装请删除目录下.env文件');
} }

View File

@ -2,15 +2,10 @@
namespace App\Console\Commands; 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 Illuminate\Console\Command;
use App\Models\Order; use App\Models\Order;
use App\Models\Stat; use App\Models\StatOrder;
use App\Models\CommissionLog; use App\Models\CommissionLog;
use Illuminate\Support\Facades\DB;
class V2boardStatistics extends Command class V2boardStatistics extends Command
{ {
@ -45,87 +40,38 @@ class V2boardStatistics extends Command
*/ */
public function handle() public function handle()
{ {
$startAt = microtime(true);
ini_set('memory_limit', -1); ini_set('memory_limit', -1);
$this->statUser(); $this->statOrder();
$this->statServer();
$this->stat();
$this->info('耗时' . (microtime(true) - $startAt));
} }
private function statServer() private function statOrder()
{
$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()
{ {
$endAt = strtotime(date('Y-m-d')); $endAt = strtotime(date('Y-m-d'));
$startAt = strtotime('-1 day', $endAt); $startAt = strtotime('-1 day', $endAt);
$statisticalService = new StatisticalService(); $orderBuilder = Order::where('paid_at', '>=', $startAt)
$statisticalService->setStartAt($startAt); ->where('paid_at', '<', $endAt)
$statisticalService->setEndAt($endAt); ->whereNotIn('status', [0, 2]);
$data = $statisticalService->generateStatData(); $orderCount = $orderBuilder->count();
$data['record_at'] = $startAt; $orderAmount = $orderBuilder->sum('total_amount');
$data['record_type'] = 'd'; $commissionLogBuilder = CommissionLog::where('created_at', '>=', $startAt)
$statistic = Stat::where('record_at', $startAt) ->where('created_at', '<', $endAt);
$commissionCount = $commissionLogBuilder->count();
$commissionAmount = $commissionLogBuilder->sum('get_amount');
$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') ->where('record_type', 'd')
->first(); ->first();
if ($statistic) { if ($statistic) {
$statistic->update($data); $statistic->update($data);
return; return;
} }
Stat::create($data); StatOrder::create($data);
} }
} }

View File

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

View File

@ -87,16 +87,28 @@ class ConfigController extends Controller
'site' => [ 'site' => [
'logo' => config('v2board.logo'), 'logo' => config('v2board.logo'),
'force_https' => (int)config('v2board.force_https', 0), '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), 'stop_register' => (int)config('v2board.stop_register', 0),
'email_verify' => (int)config('v2board.email_verify', 0),
'app_name' => config('v2board.app_name', 'V2Board'), 'app_name' => config('v2board.app_name', 'V2Board'),
'app_description' => config('v2board.app_description', 'V2Board is best!'), 'app_description' => config('v2board.app_description', 'V2Board is best!'),
'app_url' => config('v2board.app_url'), 'app_url' => config('v2board.app_url'),
'subscribe_url' => config('v2board.subscribe_url'), 'subscribe_url' => config('v2board.subscribe_url'),
'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0), 'try_out_plan_id' => (int)config('v2board.try_out_plan_id', 0),
'try_out_hour' => (int)config('v2board.try_out_hour', 1), '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'), 'tos_url' => config('v2board.tos_url'),
'currency' => config('v2board.currency', 'CNY'), 'currency' => config('v2board.currency', 'CNY'),
'currency_symbol' => config('v2board.currency_symbol', '¥'), '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),
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', hash('crc32b', config('app.key'))))
], ],
'subscribe' => [ 'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), 'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
@ -140,23 +152,6 @@ class ConfigController extends Controller
'macos_download_url' => config('v2board.macos_download_url'), 'macos_download_url' => config('v2board.macos_download_url'),
'android_version' => config('v2board.android_version'), 'android_version' => config('v2board.android_version'),
'android_download_url' => config('v2board.android_download_url') '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])) { if ($key && isset($data[$key])) {

View File

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

View File

@ -5,7 +5,7 @@ namespace App\Http\Controllers\Admin\Server;
use App\Models\Plan; use App\Models\Plan;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Models\ServerVmess; use App\Models\ServerV2ray;
use App\Models\ServerGroup; use App\Models\ServerGroup;
use App\Models\User; use App\Models\User;
use App\Services\ServerService; use App\Services\ServerService;
@ -65,7 +65,7 @@ class GroupController extends Controller
} }
} }
$servers = ServerVmess::all(); $servers = ServerV2ray::all();
foreach ($servers as $server) { foreach ($servers as $server) {
if (in_array($request->input('id'), $server->group_id)) { if (in_array($request->input('id'), $server->group_id)) {
abort(500, '该组已被节点所使用,无法删除'); 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; namespace App\Http\Controllers\Admin\Server;
use App\Models\ServerV2ray;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Services\ServerService; use App\Services\ServerService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -20,20 +23,27 @@ class ManageController extends Controller
public function sort(Request $request) public function sort(Request $request)
{ {
ini_set('post_max_size', '1m'); ini_set('post_max_size', '1m');
$params = $request->only(
'shadowsocks',
'vmess',
'trojan',
'hysteria'
) ?? [];
DB::beginTransaction(); DB::beginTransaction();
foreach ($params as $k => $v) { foreach ($request->input('sorts') as $k => $v) {
$model = 'App\\Models\\Server' . ucfirst($k); switch ($v['key']) {
foreach($v as $id => $sort) { case 'shadowsocks':
if (!$model::find($id)->update(['sort' => $sort])) { if (!ServerShadowsocks::find($v['value'])->update(['sort' => $k + 1])) {
DB::rollBack(); DB::rollBack();
abort(500, '保存失败'); 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(); DB::commit();

View File

@ -15,12 +15,6 @@ class RouteController extends Controller
public function fetch(Request $request) public function fetch(Request $request)
{ {
$routes = ServerRoute::get(); $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 [ return [
'data' => $routes 'data' => $routes
]; ];
@ -30,19 +24,10 @@ class RouteController extends Controller
{ {
$params = $request->validate([ $params = $request->validate([
'remarks' => 'required', 'remarks' => 'required',
'match' => 'required|array', 'match' => 'required',
'action' => 'required|in:block,dns', 'action' => 'required',
'action_value' => 'nullable' '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')) { if ($request->input('id')) {
try { try {
$route = ServerRoute::find($request->input('id')); $route = ServerRoute::find($request->input('id'));

View File

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

View File

@ -7,94 +7,24 @@ use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Models\StatUser; use App\Models\StatUser;
use App\Services\ServerService; use App\Services\ServerService;
use App\Services\StatisticalService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ServerGroup; use App\Models\ServerGroup;
use App\Models\ServerVmess; use App\Models\ServerV2ray;
use App\Models\Plan; use App\Models\Plan;
use App\Models\User; use App\Models\User;
use App\Models\Ticket; use App\Models\Ticket;
use App\Models\Order; use App\Models\Order;
use App\Models\Stat; use App\Models\StatOrder;
use App\Models\StatServer; use App\Models\StatServer;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class StatController extends Controller 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) public function getOverride(Request $request)
{ {
return [ return response([
'data' => [ 'data' => [
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1'))) 'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time()) ->where('created_at', '<', time())
@ -125,12 +55,12 @@ class StatController extends Controller
->where('created_at', '<', strtotime(date('Y-m-1'))) ->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('get_amount'), ->sum('get_amount'),
] ]
]; ]);
} }
public function getOrder(Request $request) public function getOrder(Request $request)
{ {
$statistics = Stat::where('record_type', 'd') $statistics = StatOrder::where('record_type', 'd')
->limit(31) ->limit(31)
->orderBy('record_at', 'DESC') ->orderBy('record_at', 'DESC')
->get() ->get()
@ -138,50 +68,49 @@ class StatController extends Controller
$result = []; $result = [];
foreach ($statistics as $statistic) { foreach ($statistics as $statistic) {
$date = date('m-d', $statistic['record_at']); $date = date('m-d', $statistic['record_at']);
$result[] = [ array_push($result, [
'type' => '收款金额', 'type' => '收款金额',
'date' => $date, 'date' => $date,
'value' => $statistic['paid_total'] / 100 'value' => $statistic['order_amount'] / 100
]; ]);
$result[] = [ array_push($result, [
'type' => '收款笔数', 'type' => '收款笔数',
'date' => $date, 'date' => $date,
'value' => $statistic['paid_count'] 'value' => $statistic['order_count']
]; ]);
$result[] = [ array_push($result, [
'type' => '佣金金额(已发放)', 'type' => '佣金金额(已发放)',
'date' => $date, 'date' => $date,
'value' => $statistic['commission_total'] / 100 'value' => $statistic['commission_amount'] / 100
]; ]);
$result[] = [ array_push($result, [
'type' => '佣金笔数(已发放)', 'type' => '佣金笔数(已发放)',
'date' => $date, 'date' => $date,
'value' => $statistic['commission_count'] 'value' => $statistic['commission_count']
]; ]);
} }
$result = array_reverse($result); $result = array_reverse($result);
return [ return response([
'data' => $result 'data' => $result
]; ]);
} }
public function getServerLastRank() public function getServerLastRank()
{ {
$servers = [ $servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(), 'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'v2ray' => ServerVmess::where('parent_id', null)->get()->toArray(), 'v2ray' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray(), 'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
'vmess' => ServerVmess::where('parent_id', null)->get()->toArray()
]; ];
$startAt = strtotime('-1 day', strtotime(date('Y-m-d'))); $startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$endAt = strtotime(date('Y-m-d')); $endAt = strtotime(date('Y-m-d'));
$statistics = StatServer::select([ $statistics = StatServer::select([
'server_id', 'server_id',
'server_type', 'server_type',
'u', 'u',
'd', 'd',
DB::raw('(u+d) as total') DB::raw('(u+d) as total')
]) ])
->where('record_at', '>=', $startAt) ->where('record_at', '>=', $startAt)
->where('record_at', '<', $endAt) ->where('record_at', '<', $endAt)
->where('record_type', 'd') ->where('record_type', 'd')
@ -198,9 +127,9 @@ class StatController extends Controller
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824; $statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
} }
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics); array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return [ return response([
'data' => $statistics 'data' => $statistics
]; ]);
} }
public function getStatUser(Request $request) public function getStatUser(Request $request)
@ -220,6 +149,5 @@ class StatController extends Controller
'total' => $total 'total' => $total
]; ];
} }
} }

View File

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

View File

@ -25,9 +25,9 @@ class ThemeController extends Controller
{ {
$themeConfigs = []; $themeConfigs = [];
foreach ($this->themes as $theme) { foreach ($this->themes as $theme) {
$themeConfigFile = $this->path . "{$theme}/config.json"; $themeConfigFile = $this->path . "{$theme}/config.php";
if (!File::exists($themeConfigFile)) continue; if (!File::exists($themeConfigFile)) continue;
$themeConfig = json_decode(File::get($themeConfigFile), true); $themeConfig = include($themeConfigFile);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue; if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
$themeConfigs[$theme] = $themeConfig; $themeConfigs[$theme] = $themeConfig;
if (config("theme.{$theme}")) continue; if (config("theme.{$theme}")) continue;
@ -60,10 +60,9 @@ class ThemeController extends Controller
]); ]);
$payload['config'] = json_decode(base64_decode($payload['config']), true); $payload['config'] = json_decode(base64_decode($payload['config']), true);
if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误'); 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, '主题不存在'); if (!File::exists($themeConfigFile)) abort(500, '主题不存在');
$themeConfig = json_decode(File::get($themeConfigFile), true); $themeConfig = include($themeConfigFile);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, '主题配置文件有误');
$validateFields = array_column($themeConfig['configs'], 'field_name'); $validateFields = array_column($themeConfig['configs'], 'field_name');
$config = []; $config = [];
foreach ($validateFields as $validateField) { 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\UserSendMail;
use App\Http\Requests\Admin\UserUpdate; use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob; use App\Jobs\SendEmailJob;
use App\Services\AuthService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -129,11 +128,6 @@ class UserController extends Controller
$params['invite_user_id'] = null; $params['invite_user_id'] = null;
} }
if (isset($params['banned']) && (int)$params['banned'] === 1) {
$authService = new AuthService($user);
$authService->removeAllSession();
}
try { try {
$user->update($params); $user->update($params);
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -5,7 +5,9 @@ namespace App\Http\Controllers\Client;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\ServerService; use App\Services\ServerService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\Clash;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\ServerV2ray;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@ -42,7 +44,7 @@ class AppController extends Controller
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item)); array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item)); array_push($proxy, Protocols\Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }

View File

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

View File

@ -49,7 +49,7 @@ class Clash
array_push($proxy, self::buildShadowsocks($user['uuid'], $item)); array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item)); array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }
@ -77,18 +77,12 @@ class Clash
if ($isFilter) continue; if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
} }
$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 // Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST']; $subsDomain = $_SERVER['SERVER_NAME'];
if ($subsDomain) { $subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], "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); $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml; return $yaml;
} }
@ -128,11 +122,6 @@ class Clash
$array['servername'] = $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') { if ($server['network'] === 'ws') {
$array['network'] = 'ws'; $array['network'] = 'ws';
if ($server['networkSettings']) { 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 = ''; $uri = '';
foreach ($servers as $item) { foreach ($servers as $item) {
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item); $uri .= self::buildVmess($user['uuid'], $item);
} }
if ($item['type'] === 'shadowsocks') { if ($item['type'] === 'shadowsocks') {
@ -68,11 +68,6 @@ class Passwall
$config['sni'] = $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') { if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings']; $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ class Shadowrocket
if ($item['type'] === 'shadowsocks') { if ($item['type'] === 'shadowsocks') {
$uri .= self::buildShadowsocks($user['uuid'], $item); $uri .= self::buildShadowsocks($user['uuid'], $item);
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item); $uri .= self::buildVmess($user['uuid'], $item);
} }
if ($item['type'] === 'trojan') { if ($item['type'] === 'trojan') {
@ -46,12 +46,12 @@ class Shadowrocket
public static function buildShadowsocks($password, $server) public static function buildShadowsocks($password, $server)
{ {
if ($server['cipher'] === '2022-blake3-aes-128-gcm') { if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 16); $serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16); $userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}"; $password = "{$serverKey}:{$userKey}";
} }
if ($server['cipher'] === '2022-blake3-aes-256-gcm') { if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getServerKey($server['created_at'], 32); $serverKey = Helper::getShadowsocksServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32); $userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}"; $password = "{$serverKey}:{$userKey}";
} }
@ -82,15 +82,6 @@ class Shadowrocket
$config['peer'] = $tlsSettings['serverName']; $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') { if ($server['network'] === 'ws') {
$config['obfs'] = "websocket"; $config['obfs'] = "websocket";
if ($server['networkSettings']) { if ($server['networkSettings']) {

View File

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

View File

@ -47,7 +47,7 @@ class Stash
array_push($proxy, self::buildShadowsocks($user['uuid'], $item)); array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
array_push($proxy, self::buildVmess($user['uuid'], $item)); array_push($proxy, self::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']); array_push($proxies, $item['name']);
} }
@ -75,17 +75,12 @@ class Stash
if ($isFilter) continue; if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
} }
$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 // Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST']; $subsDomain = $_SERVER['SERVER_NAME'];
if ($subsDomain) { $subsDomainRule = "DOMAIN,{$subsDomain},DIRECT";
array_unshift($config['rules'], "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); $yaml = str_replace('$app_name', config('v2board.app_name', 'V2Board'), $yaml);
return $yaml; return $yaml;
} }
@ -125,11 +120,6 @@ class Stash
$array['servername'] = $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') { if ($server['network'] === 'ws') {
$array['network'] = 'ws'; $array['network'] = 'ws';
if ($server['networkSettings']) { if ($server['networkSettings']) {

View File

@ -41,7 +41,7 @@ class Surfboard
// [Proxy Group] // [Proxy Group]
$proxyGroup .= $item['name'] . ', '; $proxyGroup .= $item['name'] . ', ';
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
// [Proxy] // [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item); $proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group] // [Proxy Group]
@ -65,21 +65,12 @@ class Surfboard
// Subscription link // Subscription link
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); $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_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config); $config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config); $config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config); $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
$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; return $config;
} }

View File

@ -41,7 +41,7 @@ class Surge
// [Proxy Group] // [Proxy Group]
$proxyGroup .= $item['name'] . ', '; $proxyGroup .= $item['name'] . ', ';
} }
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
// [Proxy] // [Proxy]
$proxies .= self::buildVmess($user['uuid'], $item); $proxies .= self::buildVmess($user['uuid'], $item);
// [Proxy Group] // [Proxy Group]
@ -65,22 +65,13 @@ class Surge
// Subscription link // Subscription link
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}"); $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']; $subsURL = 'https://' . $subsDomain . '/api/v1/client/subscribe?token=' . $user['token'];
$config = str_replace('$subs_link', $subsURL, $config); $config = str_replace('$subs_link', $subsURL, $config);
$config = str_replace('$subs_domain', $subsDomain, $config); $config = str_replace('$subs_domain', $subsDomain, $config);
$config = str_replace('$proxies', $proxies, $config); $config = str_replace('$proxies', $proxies, $config);
$config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config); $config = str_replace('$proxy_group', rtrim($proxyGroup, ', '), $config);
$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; return $config;
} }

View File

@ -3,8 +3,6 @@
namespace App\Http\Controllers\Client\Protocols; namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class V2rayN class V2rayN
{ {
public $flag = 'v2rayn'; public $flag = 'v2rayn';
@ -24,7 +22,7 @@ class V2rayN
$uri = ''; $uri = '';
foreach ($servers as $item) { foreach ($servers as $item) {
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item); $uri .= self::buildVmess($user['uuid'], $item);
} }
if ($item['type'] === 'shadowsocks') { if ($item['type'] === 'shadowsocks') {
@ -39,16 +37,6 @@ class V2rayN
public static function buildShadowsocks($password, $server) 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']); $name = rawurlencode($server['name']);
$str = str_replace( $str = str_replace(
['+', '/', '='], ['+', '/', '='],
@ -80,11 +68,6 @@ class V2rayN
$config['sni'] = $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') { if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings']; $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -22,7 +22,7 @@ class V2rayNG
$uri = ''; $uri = '';
foreach ($servers as $item) { foreach ($servers as $item) {
if ($item['type'] === 'vmess') { if ($item['type'] === 'v2ray') {
$uri .= self::buildVmess($user['uuid'], $item); $uri .= self::buildVmess($user['uuid'], $item);
} }
if ($item['type'] === 'shadowsocks') { if ($item['type'] === 'shadowsocks') {
@ -68,11 +68,6 @@ class V2rayNG
$config['sni'] = $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') { if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings']; $wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path']; if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -23,6 +23,19 @@ class CommController extends Controller
'app_description' => config('v2board.app_description'), 'app_description' => config('v2board.app_description'),
'app_url' => config('v2board.app_url'), 'app_url' => config('v2board.app_url'),
'logo' => config('v2board.logo'), 'logo' => config('v2board.logo'),
// TODO:REMOVE:1.7.0
'tosUrl' => config('v2board.tos_url'),
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0,
'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
'appDescription' => config('v2board.app_description'),
'appUrl' => config('v2board.app_url'),
] ]
]); ]);
} }

View File

@ -17,6 +17,7 @@ use App\Utils\Helper;
use App\Utils\Dict; use App\Utils\Dict;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha; use ReCaptcha\ReCaptcha;
use Firebase\JWT\JWT;
class AuthController extends Controller class AuthController extends Controller
{ {
@ -26,7 +27,7 @@ class AuthController extends Controller
abort(404); abort(404);
} }
$params = $request->validate([ $params = $request->validate([
'email' => 'required|email:strict', 'email' => 'required|email',
'redirect' => 'nullable' 'redirect' => 'nullable'
]); ]);
@ -116,7 +117,7 @@ class AuthController extends Controller
if (empty($request->input('email_code'))) { if (empty($request->input('email_code'))) {
abort(500, __('Email verification code cannot be empty')); 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')); abort(500, __('Incorrect email verification code'));
} }
} }
@ -156,7 +157,6 @@ class AuthController extends Controller
$user->plan_id = $plan->id; $user->plan_id = $plan->id;
$user->group_id = $plan->group_id; $user->group_id = $plan->group_id;
$user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600); $user->expired_at = time() + (config('v2board.try_out_hour', 1) * 3600);
$user->speed_limit = $plan->speed_limit;
} }
} }
@ -181,7 +181,7 @@ class AuthController extends Controller
$authService = new AuthService($user); $authService = new AuthService($user);
return response()->json([ return response()->json([
'data' => $authService->generateAuthData($request) 'data' => $authService->generateAuthData('register')
]); ]);
} }
@ -190,13 +190,10 @@ class AuthController extends Controller
$email = $request->input('email'); $email = $request->input('email');
$password = $request->input('password'); $password = $request->input('password');
if ((int)config('v2board.password_limit_enable', 1)) { $passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
if ($passwordErrorCount >= (int)config('v2board.password_limit_count', 5)) { if ($passwordErrorCount >= 5) {
abort(500, __('There are too many password errors, please try again after :minute minutes.', [ abort(500, __('There are too many password errors, please try again after 30 minutes.'));
'minute' => config('v2board.password_limit_expire', 60)
]));
}
} }
$user = User::where('email', $email)->first(); $user = User::where('email', $email)->first();
@ -209,13 +206,11 @@ class AuthController extends Controller
$password, $password,
$user->password) $user->password)
) { ) {
if ((int)config('v2board.password_limit_enable')) { Cache::put(
Cache::put( CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
CacheKey::get('PASSWORD_ERROR_LIMIT', $email), (int)$passwordErrorCount + 1,
(int)$passwordErrorCount + 1, 30 * 60
60 * (int)config('v2board.password_limit_expire', 60) );
);
}
abort(500, __('Incorrect email or password')); abort(500, __('Incorrect email or password'));
} }
@ -225,7 +220,7 @@ class AuthController extends Controller
$authService = new AuthService($user); $authService = new AuthService($user);
return response([ return response([
'data' => $authService->generateAuthData($request) 'data' => $authService->generateAuthData('login')
]); ]);
} }
@ -257,7 +252,7 @@ class AuthController extends Controller
Cache::forget($key); Cache::forget($key);
$authService = new AuthService($user); $authService = new AuthService($user);
return response([ return response([
'data' => $authService->generateAuthData($request) 'data' => $authService->generateAuthData('token')
]); ]);
} }
} }
@ -286,7 +281,7 @@ class AuthController extends Controller
public function forget(AuthForget $request) 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')); abort(500, __('Incorrect email verification code'));
} }
$user = User::where('email', $request->input('email'))->first(); $user = User::where('email', $request->input('email'))->first();

View File

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

View File

@ -4,7 +4,6 @@ namespace App\Http\Controllers\Server;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Services\ServerService; use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Http\Request; 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_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
$formatData = [];
foreach ($data as $item) { 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([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -3,7 +3,6 @@
namespace App\Http\Controllers\Server; namespace App\Http\Controllers\Server;
use App\Services\ServerService; use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Http\Request; 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_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600); Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
$formatData = [];
foreach ($data as $item) { 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([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -3,14 +3,13 @@
namespace App\Http\Controllers\Server; namespace App\Http\Controllers\Server;
use App\Services\ServerService; use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\ServerVmess; use App\Models\ServerV2ray;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
@ -31,7 +30,6 @@ class UniProxyController extends Controller
abort(500, 'token is error'); abort(500, 'token is error');
} }
$this->nodeType = $request->input('node_type'); $this->nodeType = $request->input('node_type');
if ($this->nodeType === 'v2ray') $this->nodeType = 'vmess';
$this->nodeId = $request->input('node_id'); $this->nodeId = $request->input('node_id');
$this->serverService = new ServerService(); $this->serverService = new ServerService();
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType); $this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
@ -64,7 +62,11 @@ class UniProxyController extends Controller
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), count($data), 3600); 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); Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
$userService = new UserService(); $userService = new UserService();
$userService->trafficFetch($this->nodeInfo->toArray(), $this->nodeType, $data); foreach (array_keys($data) as $k) {
$u = $data[$k][0];
$d = $data[$k][1];
$userService->trafficFetch($u, $d, $k, $this->nodeInfo->toArray(), $this->nodeType);
}
return response([ return response([
'data' => true 'data' => true
@ -84,13 +86,13 @@ class UniProxyController extends Controller
]; ];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') { if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 16); $response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 16);
} }
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') { if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 32); $response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 32);
} }
break; break;
case 'vmess': case 'v2ray':
$response = [ $response = [
'server_port' => $this->nodeInfo->server_port, 'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network, 'network' => $this->nodeInfo->network,
@ -102,23 +104,13 @@ class UniProxyController extends Controller
$response = [ $response = [
'host' => $this->nodeInfo->host, 'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port, 'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name, '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)
]; ];
break; break;
} }
$response['base_config'] = [ $response['base_config'] = [
'push_interval' => (int)config('v2board.server_push_interval', 60), 'push_interval' => config('v2board.server_push_interval', 60),
'pull_interval' => (int)config('v2board.server_pull_interval', 60) 'pull_interval' => config('v2board.server_pull_interval', 60)
]; ];
if ($this->nodeInfo['route_id']) { if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']); $response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);

View File

@ -191,7 +191,6 @@ class OrderController extends Controller
$payment = Payment::find($method); $payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available')); if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id); $paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) { 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); $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 App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use App\Models\ServerVmess; use App\Models\ServerV2ray;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Models\User; use App\Models\User;
@ -26,7 +26,8 @@ class ServerController extends Controller
$serverService = new ServerService(); $serverService = new ServerService();
$servers = $serverService->getAvailableServers($user); $servers = $serverService->getAvailableServers($user);
} }
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
$eTag = sha1(json_encode(array_column($servers, 'updated_at')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) { if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304); abort(304);
} }

View File

@ -6,7 +6,6 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserTransfer; use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate; use App\Http\Requests\User\UserUpdate;
use App\Http\Requests\User\UserChangePassword; use App\Http\Requests\User\UserChangePassword;
use App\Services\AuthService;
use App\Services\UserService; use App\Services\UserService;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -19,30 +18,6 @@ use Illuminate\Support\Facades\Cache;
class UserController extends Controller 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) public function checkLogin(Request $request)
{ {
$data = [ $data = [

View File

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

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

@ -24,7 +24,9 @@ class ConfigSave extends FormRequest
// site // site
'logo' => 'nullable|url', 'logo' => 'nullable|url',
'force_https' => 'in:0,1', 'force_https' => 'in:0,1',
'safe_mode_enable' => 'in:0,1',
'stop_register' => 'in:0,1', 'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1',
'app_name' => '', 'app_name' => '',
'app_description' => '', 'app_description' => '',
'app_url' => 'nullable|url', 'app_url' => 'nullable|url',
@ -32,9 +34,19 @@ class ConfigSave extends FormRequest
'try_out_enable' => 'in:0,1', 'try_out_enable' => 'in:0,1',
'try_out_plan_id' => 'integer', 'try_out_plan_id' => 'integer',
'try_out_hour' => 'numeric', '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', 'tos_url' => 'nullable|url',
'currency' => '', 'currency' => '',
'currency_symbol' => '', 'currency_symbol' => '',
'register_limit_by_ip_enable' => 'in:0,1',
'register_limit_count' => 'integer',
'register_limit_expire' => 'integer',
'secure_path' => '',
// subscribe // subscribe
'plan_change_enable' => 'in:0,1', 'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1,2,3,4', 'reset_traffic_method' => 'in:0,1,2,3,4',
@ -73,23 +85,7 @@ class ConfigSave extends FormRequest
'macos_version' => '', 'macos_version' => '',
'macos_download_url' => '', 'macos_download_url' => '',
'android_version' => '', 'android_version' => '',
'android_download_url' => '', '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',
]; ];
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
@ -110,9 +106,7 @@ class ConfigSave extends FormRequest
'server_token.min' => '通讯密钥长度必须大于16位', 'server_token.min' => '通讯密钥长度必须大于16位',
'tos_url.url' => '服务条款URL格式不正确必须携带http(s)://', 'tos_url.url' => '服务条款URL格式不正确必须携带http(s)://',
'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式必须携带http(s)://', 'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式必须携带http(s)://',
'logo.url' => 'LOGO URL格式不正确必须携带https(s)://', 'logo.url' => 'LOGO URL格式不正确必须携带https(s)://'
'secure_path.min' => '后台路径长度最小为8位',
'secure_path.regex' => '后台路径只能为字母或数字'
]; ];
} }
} }

View File

@ -14,7 +14,7 @@ class OrderFetch extends FormRequest
public function rules() public function rules()
{ {
return [ 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.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => '' 'filter.*.value' => ''
]; ];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,6 +20,7 @@ class PassportRoute
// Comm // Comm
$router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify'); $router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');
$router->post('/comm/pv', 'Passport\\CommController@pv'); $router->post('/comm/pv', 'Passport\\CommController@pv');
$router->get ('/comm/config', 'Guest\\CommController@config'); // TODO:REMOVE:1.7.0
}); });
} }
} }

View File

@ -21,12 +21,11 @@ class UserRoute
$router->get ('/checkLogin', 'User\\UserController@checkLogin'); $router->get ('/checkLogin', 'User\\UserController@checkLogin');
$router->post('/transfer', 'User\\UserController@transfer'); $router->post('/transfer', 'User\\UserController@transfer');
$router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl'); $router->post('/getQuickLoginUrl', 'User\\UserController@getQuickLoginUrl');
$router->get ('/getActiveSession', 'User\\UserController@getActiveSession');
$router->post('/removeActiveSession', 'User\\UserController@removeActiveSession');
// Order // Order
$router->post('/order/save', 'User\\OrderController@save'); $router->post('/order/save', 'User\\OrderController@save');
$router->post('/order/checkout', 'User\\OrderController@checkout'); $router->post('/order/checkout', 'User\\OrderController@checkout');
$router->get ('/order/check', 'User\\OrderController@check'); $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/detail', 'User\\OrderController@detail');
$router->get ('/order/fetch', 'User\\OrderController@fetch'); $router->get ('/order/fetch', 'User\\OrderController@fetch');
$router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod'); $router->get ('/order/getPaymentMethod', 'User\\OrderController@getPaymentMethod');

View File

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

View File

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

View File

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

@ -4,9 +4,9 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; 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 $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [ protected $casts = [

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; 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 $dateFormat = 'U';
protected $guarded = ['id']; protected $guarded = ['id'];
protected $casts = [ protected $casts = [

View File

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

View File

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

View File

@ -2,48 +2,40 @@
namespace App\Services; namespace App\Services;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Firebase\JWT\JWT; use Firebase\JWT\JWT;
use Firebase\JWT\Key; use Firebase\JWT\Key;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Http\Request;
class AuthService class AuthService
{ {
private $user; private $user;
public function __construct(User $user) public function __construct($user)
{ {
$this->user = $user; $this->user = $user;
} }
public function generateAuthData(Request $request) public function generateAuthData($utm)
{ {
$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 [ return [
'token' => $this->user->token, 'token' => $this->user->token,
'is_admin' => $this->user->is_admin, 'is_admin' => $this->user->is_admin,
'auth_data' => $authData 'auth_data' => JWT::encode([
'expired_at' => time() + 3600,
'id' => $this->user->id,
'utm' => $utm,
], config('app.key'), 'HS256')
]; ];
} }
public static function decryptAuthData($jwt) public static function decryptAuthData($jwt)
{ {
try { try {
if (!Cache::has($jwt)) { if (!Cache::has($jwt)) {
$data = (array)JWT::decode($jwt, new Key(config('app.key'), 'HS256')); $data = (array)JWT::decode($jwt, new Key(config('app.key'), 'HS256'));
if (!self::checkSession($data['id'], $data['session'])) return false; if ($data['expired_at'] < time()) return false;
$user = User::select([ $user = User::select([
'id', 'id',
'email', 'email',
@ -59,46 +51,4 @@ class AuthService
return false; 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

@ -184,35 +184,43 @@ class OrderService
$order->surplus_order_ids = array_column($orderModel->get()->toArray(), 'id'); $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) 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', '!=', 'reset_price')
->where('period', '!=', 'onetime_price') ->where('status', 3);
->where('status', 3) $orders = $orderModel->get();
->get() $orderSurplusMonth = 0;
->toArray(); $orderSurplusAmount = 0;
if (!$orders) return; $userSurplusMonth = ($user->expired_at - time()) / 2678400;
$orderAmountSum = 0; foreach ($orders as $k => $item) {
$orderMonthSum = 0; // 兼容历史余留问题
$lastValidateAt = 0; if ($item->period === 'onetime_price') continue;
foreach ($orders as $item) { if ($this->orderIsUsed($item)) continue;
$period = self::STR_TO_TIME[$item['period']]; $orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->period];
if (strtotime("+{$period} month", $item['created_at']) < time()) continue; $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']);
$lastValidateAt = $item['created_at']; }
$orderMonthSum = $period + $orderMonthSum; if (!$orderSurplusMonth || !$orderSurplusAmount) return;
$orderAmountSum = $orderAmountSum + ($item['total_amount'] + $item['balance_amount'] + $item['surplus_amount'] - $item['refund_amount']); $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_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) public function paid(string $callbackNo)
@ -223,11 +231,7 @@ class OrderService
$order->paid_at = time(); $order->paid_at = time();
$order->callback_no = $callbackNo; $order->callback_no = $callbackNo;
if (!$order->save()) return false; if (!$order->save()) return false;
try { OrderHandleJob::dispatch($order->trade_no);
OrderHandleJob::dispatchNow($order->trade_no);
} catch (\Exception $e) {
return false;
}
return true; return true;
} }

View File

@ -2,12 +2,11 @@
namespace App\Services; namespace App\Services;
use App\Models\ServerHysteria;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Models\ServerRoute; use App\Models\ServerRoute;
use App\Models\ServerShadowsocks; use App\Models\ServerShadowsocks;
use App\Models\User; use App\Models\User;
use App\Models\ServerVmess; use App\Models\ServerV2ray;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use App\Utils\Helper; use App\Utils\Helper;
@ -16,113 +15,97 @@ use Illuminate\Support\Facades\Cache;
class ServerService class ServerService
{ {
public function getAvailableVmess(User $user):array public function getV2ray(User $user, $all = false):array
{ {
$servers = []; $servers = [];
$model = ServerVmess::orderBy('sort', 'ASC'); $model = ServerV2ray::orderBy('sort', 'ASC');
$vmess = $model->get(); if (!$all) {
foreach ($vmess as $key => $v) { $model->where('show', 1);
if (!$v['show']) continue; }
$vmess[$key]['type'] = 'vmess'; $v2ray = $model->get();
if (!in_array($user->group_id, $vmess[$key]['group_id'])) continue; for ($i = 0; $i < count($v2ray); $i++) {
if (strpos($vmess[$key]['port'], '-') !== false) { $v2ray[$i]['type'] = 'v2ray';
$vmess[$key]['port'] = Helper::randomPort($vmess[$key]['port']); $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']) { if ($v2ray[$i]['parent_id']) {
$vmess[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $vmess[$key]['parent_id'])); $v2ray[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $v2ray[$i]['parent_id']));
} else { } 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; return $servers;
} }
public function getAvailableTrojan(User $user):array public function getTrojan(User $user, $all = false):array
{ {
$servers = []; $servers = [];
$model = ServerTrojan::orderBy('sort', 'ASC'); $model = ServerTrojan::orderBy('sort', 'ASC');
if (!$all) {
$model->where('show', 1);
}
$trojan = $model->get(); $trojan = $model->get();
foreach ($trojan as $key => $v) { for ($i = 0; $i < count($trojan); $i++) {
if (!$v['show']) continue; $trojan[$i]['type'] = 'trojan';
$trojan[$key]['type'] = 'trojan'; $groupId = $trojan[$i]['group_id'];
if (!in_array($user->group_id, $trojan[$key]['group_id'])) continue; if (!in_array($user->group_id, $groupId)) continue;
if (strpos($trojan[$key]['port'], '-') !== false) { if (strpos($trojan[$i]['port'], '-') !== false) {
$trojan[$key]['port'] = Helper::randomPort($trojan[$key]['port']); $trojan[$i]['port'] = Helper::randomPort($trojan[$i]['port']);
} }
if ($trojan[$key]['parent_id']) { if ($trojan[$i]['parent_id']) {
$trojan[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$key]['parent_id'])); $trojan[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojan[$i]['parent_id']));
} else { } 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; return $servers;
} }
public function getAvailableHysteria(User $user) public function getShadowsocks(User $user, $all = false)
{
$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)
{ {
$servers = []; $servers = [];
$model = ServerShadowsocks::orderBy('sort', 'ASC'); $model = ServerShadowsocks::orderBy('sort', 'ASC');
$shadowsocks = $model->get()->keyBy('id'); if (!$all) {
foreach ($shadowsocks as $key => $v) { $model->where('show', 1);
if (!$v['show']) continue; }
$shadowsocks[$key]['type'] = 'shadowsocks'; $shadowsocks = $model->get();
$shadowsocks[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $v['id'])); for ($i = 0; $i < count($shadowsocks); $i++) {
if (!in_array($user->group_id, $v['group_id'])) continue; $shadowsocks[$i]['type'] = 'shadowsocks';
if (strpos($v['port'], '-') !== false) { $groupId = $shadowsocks[$i]['group_id'];
$shadowsocks[$key]['port'] = Helper::randomPort($v['port']); 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']])) { if ($shadowsocks[$i]['parent_id']) {
$shadowsocks[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $v['parent_id'])); $shadowsocks[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsocks[$i]['parent_id']));
$shadowsocks[$key]['created_at'] = $shadowsocks[$v['parent_id']]['created_at']; } 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; return $servers;
} }
public function getAvailableServers(User $user) public function getAvailableServers(User $user, $all = false)
{ {
$servers = array_merge( $servers = array_merge(
$this->getAvailableShadowsocks($user), $this->getShadowsocks($user, $all),
$this->getAvailableVmess($user), $this->getV2ray($user, $all),
$this->getAvailableTrojan($user), $this->getTrojan($user, $all)
$this->getAvailableHysteria($user)
); );
$tmp = array_column($servers, 'sort'); $tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers); array_multisort($tmp, SORT_ASC, $servers);
return array_map(function ($server) { $servers = array_map(function ($server) {
$server['port'] = (int)$server['port']; $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; return $server;
}, $servers); }, $servers);
return $servers;
} }
public function getAvailableUsers($groupId) public function getAvailableUsers($groupId)
@ -136,8 +119,7 @@ class ServerService
->where('banned', 0) ->where('banned', 0)
->select([ ->select([
'id', 'id',
'uuid', 'uuid'
'speed_limit'
]) ])
->get(); ->get();
} }
@ -174,57 +156,45 @@ class ServerService
} }
} }
public function getAllShadowsocks() public function getShadowsocksServers()
{ {
$servers = ServerShadowsocks::orderBy('sort', 'ASC') $server = ServerShadowsocks::orderBy('sort', 'ASC')->get();
->get() for ($i = 0; $i < count($server); $i++) {
->toArray(); $server[$i]['type'] = 'shadowsocks';
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'shadowsocks';
} }
return $servers; return $server->toArray();
} }
public function getAllVMess() public function getV2rayServers()
{ {
$servers = ServerVmess::orderBy('sort', 'ASC') $server = ServerV2ray::orderBy('sort', 'ASC')->get();
->get() for ($i = 0; $i < count($server); $i++) {
->toArray(); $server[$i]['type'] = 'v2ray';
foreach ($servers as $k => $v) {
$servers[$k]['type'] = 'vmess';
} }
return $servers; return $server->toArray();
} }
public function getAllTrojan() public function getTrojanServers()
{ {
$servers = ServerTrojan::orderBy('sort', 'ASC') $server = ServerTrojan::orderBy('sort', 'ASC')->get();
->get() for ($i = 0; $i < count($server); $i++) {
->toArray(); $server[$i]['type'] = 'trojan';
foreach ($servers as $k => $v) {
$servers[$k]['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) private function mergeData(&$servers)
{ {
foreach ($servers as $k => $v) { foreach ($servers as $k => $v) {
$serverType = strtoupper($v['type']); $serverType = strtoupper($servers[$k]['type']);
$servers[$k]['online'] = Cache::get(CacheKey::get("SERVER_{$serverType}_ONLINE_USER", $v['parent_id'] ?? $v['id'])); $servers[$k]['online'] = Cache::get(CacheKey::get("SERVER_{$serverType}_ONLINE_USER", $servers[$k]['parent_id'] ? $servers[$k]['parent_id'] : $servers[$k]['id']));
$servers[$k]['last_check_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_CHECK_AT", $v['parent_id'] ?? $v['id'])); if ($servers[$k]['parent_id']) {
$servers[$k]['last_push_at'] = Cache::get(CacheKey::get("SERVER_{$serverType}_LAST_PUSH_AT", $v['parent_id'] ?? $v['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']) { if ((time() - 300) >= $servers[$k]['last_check_at']) {
$servers[$k]['available_status'] = 0; $servers[$k]['available_status'] = 0;
} else if ((time() - 300) >= $servers[$k]['last_push_at']) { } else if ((time() - 300) >= $servers[$k]['last_push_at']) {
@ -238,10 +208,9 @@ class ServerService
public function getAllServers() public function getAllServers()
{ {
$servers = array_merge( $servers = array_merge(
$this->getAllShadowsocks(), $this->getShadowsocksServers(),
$this->getAllVMess(), $this->getV2rayServers(),
$this->getAllTrojan(), $this->getTrojanServers()
$this->getAllHysteria()
); );
$this->mergeData($servers); $this->mergeData($servers);
$tmp = array_column($servers, 'sort'); $tmp = array_column($servers, 'sort');
@ -251,27 +220,18 @@ class ServerService
public function getRoutes(array $routeIds) public function getRoutes(array $routeIds)
{ {
$routes = ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get(); return 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) public function getServer($serverId, $serverType)
{ {
switch ($serverType) { switch ($serverType) {
case 'vmess': case 'v2ray':
return ServerVmess::find($serverId); return ServerV2ray::find($serverId);
case 'shadowsocks': case 'shadowsocks':
return ServerShadowsocks::find($serverId); return ServerShadowsocks::find($serverId);
case 'trojan': case 'trojan':
return ServerTrojan::find($serverId); return ServerTrojan::find($serverId);
case 'hysteria':
return ServerHysteria::find($serverId);
default: default:
return false; 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() public function init()
{ {
$themeConfigFile = $this->path . "{$this->theme}/config.json"; $themeConfigFile = $this->path . "{$this->theme}/config.php";
if (!File::exists($themeConfigFile)) abort(500, "{$this->theme}主题不存在"); if (!File::exists($themeConfigFile)) return;
$themeConfig = json_decode(File::get($themeConfigFile), true); $themeConfig = include($themeConfigFile);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, "{$this->theme}主题配置文件有误");
$configs = $themeConfig['configs']; $configs = $themeConfig['configs'];
$data = []; $data = [];
foreach ($configs as $config) { foreach ($configs as $config) {

View File

@ -2,12 +2,17 @@
namespace App\Services; namespace App\Services;
use App\Jobs\ServerLogJob;
use App\Jobs\StatServerJob; use App\Jobs\StatServerJob;
use App\Jobs\StatUserJob; use App\Jobs\StatUserJob;
use App\Jobs\TrafficFetchJob; use App\Jobs\TrafficFetchJob;
use App\Models\InviteCode;
use App\Models\Order; use App\Models\Order;
use App\Models\Plan; use App\Models\Plan;
use App\Models\ServerV2ray;
use App\Models\Ticket;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\DB;
class UserService class UserService
{ {
@ -28,9 +33,9 @@ class UserService
} }
if ((int)$day >= (int)$today) { if ((int)$day >= (int)$today) {
return $day - $today; return $day - $today;
} else {
return $lastDay - $today + $day;
} }
return $lastDay - $today + $day;
} }
private function calcResetDayByYearFirstDay(): int private function calcResetDayByYearFirstDay(): int
@ -44,9 +49,6 @@ class UserService
$md = date('m-d', $expiredAt); $md = date('m-d', $expiredAt);
$nowYear = strtotime(date("Y-{$md}")); $nowYear = strtotime(date("Y-{$md}"));
$nextYear = strtotime('+1 year', $nowYear); $nextYear = strtotime('+1 year', $nowYear);
if ($nowYear > time()) {
return (int)(($nowYear - time()) / 86400);
}
return (int)(($nextYear - time()) / 86400); return (int)(($nextYear - time()) / 86400);
} }
@ -168,18 +170,10 @@ class UserService
return true; 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(); TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
$statService->setStartAt(strtotime(date('Y-m-d'))); StatServerJob::dispatch($u, $d, $server, $protocol, 'd');
$statService->setUserStats(); StatUserJob::dispatch($u, $d, $userId, $server, $protocol, 'd');
$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);
}
} }
} }

View File

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

View File

@ -9,7 +9,7 @@ class Helper
return base64_encode(substr($uuid, 0, $length)); return base64_encode(substr($uuid, 0, $length));
} }
public static function getServerKey($timestamp, $length) public static function getShadowsocksServerKey($timestamp, $length)
{ {
return base64_encode(substr(md5($timestamp), 0, $length)); return base64_encode(substr(md5($timestamp), 0, $length));
} }
@ -30,8 +30,8 @@ class Helper
public static function generateOrderNo(): string public static function generateOrderNo(): string
{ {
$randomChar = mt_rand(10000, 99999); $randomChar = rand(10000, 99999);
return date('YmdHms') . substr(microtime(), 2, 6) . $randomChar; return date('YmdHms') . $randomChar;
} }
public static function exchange($from, $to) public static function exchange($from, $to)

View File

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

View File

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

View File

@ -16,7 +16,7 @@ return [
| |
*/ */
'default' => 'mysql', 'default' => env('LOG_CHANNEL', 'stack'),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -34,11 +34,6 @@ return [
*/ */
'channels' => [ 'channels' => [
'mysql' => [
'driver' => 'custom',
'via' => App\Logging\MysqlLogger::class,
],
'stack' => [ 'stack' => [
'driver' => 'stack', 'driver' => 'stack',
'channels' => ['daily'], 'channels' => ['daily'],

View File

@ -1,4 +1,4 @@
-- Adminer 4.7.7 MySQL dump -- Adminer 4.8.1 MySQL 5.7.29 dump
SET NAMES utf8; SET NAMES utf8;
SET time_zone = '+00:00'; SET time_zone = '+00:00';
@ -81,23 +81,6 @@ CREATE TABLE `v2_knowledge` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知識庫'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知識庫';
DROP TABLE IF EXISTS `v2_log`;
CREATE TABLE `v2_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`level` varchar(11) DEFAULT NULL,
`host` varchar(255) DEFAULT NULL,
`uri` varchar(255) NOT NULL,
`method` varchar(11) NOT NULL,
`data` text,
`ip` varchar(128) DEFAULT NULL,
`context` text,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_mail_log`; DROP TABLE IF EXISTS `v2_mail_log`;
CREATE TABLE `v2_mail_log` ( CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -151,8 +134,7 @@ CREATE TABLE `v2_order` (
`paid_at` int(11) DEFAULT NULL, `paid_at` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`)
UNIQUE KEY `trade_no` (`trade_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ -180,12 +162,12 @@ CREATE TABLE `v2_plan` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL, `group_id` int(11) NOT NULL,
`transfer_enable` int(11) NOT NULL, `transfer_enable` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
`speed_limit` int(11) DEFAULT NULL, `speed_limit` int(11) DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0', `show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL, `sort` int(11) DEFAULT NULL,
`renew` tinyint(1) NOT NULL DEFAULT '1', `renew` tinyint(1) NOT NULL DEFAULT '1',
`content` text, `content` text CHARACTER SET utf8mb4,
`month_price` int(11) DEFAULT NULL, `month_price` int(11) DEFAULT NULL,
`quarter_price` int(11) DEFAULT NULL, `quarter_price` int(11) DEFAULT NULL,
`half_year_price` int(11) DEFAULT NULL, `half_year_price` int(11) DEFAULT NULL,
@ -199,7 +181,7 @@ CREATE TABLE `v2_plan` (
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_group`; DROP TABLE IF EXISTS `v2_server_group`;
@ -212,35 +194,11 @@ CREATE TABLE `v2_server_group` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_hysteria`;
CREATE TABLE `v2_server_hysteria` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`route_id` varchar(255) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` varchar(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`up_mbps` int(11) NOT NULL,
`down_mbps` int(11) NOT NULL,
`server_name` varchar(64) DEFAULT NULL,
`insecure` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_server_route`; DROP TABLE IF EXISTS `v2_server_route`;
CREATE TABLE `v2_server_route` ( CREATE TABLE `v2_server_route` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`remarks` varchar(255) NOT NULL, `remarks` varchar(255) NOT NULL,
`match` text NOT NULL, `match` varchar(255) NOT NULL,
`action` varchar(11) NOT NULL, `action` varchar(11) NOT NULL,
`action_value` varchar(255) DEFAULT NULL, `action_value` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
@ -294,8 +252,8 @@ CREATE TABLE `v2_server_trojan` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='trojan伺服器表';
DROP TABLE IF EXISTS `v2_server_vmess`; DROP TABLE IF EXISTS `v2_server_v2ray`;
CREATE TABLE `v2_server_vmess` ( CREATE TABLE `v2_server_v2ray` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL, `group_id` varchar(255) NOT NULL,
`route_id` varchar(255) DEFAULT NULL, `route_id` varchar(255) DEFAULT NULL,
@ -307,7 +265,7 @@ CREATE TABLE `v2_server_vmess` (
`tls` tinyint(4) NOT NULL DEFAULT '0', `tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL, `tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL, `rate` varchar(11) NOT NULL,
`network` varchar(11) NOT NULL, `network` text NOT NULL,
`rules` text, `rules` text,
`networkSettings` text, `networkSettings` text,
`tlsSettings` text, `tlsSettings` text,
@ -321,24 +279,19 @@ CREATE TABLE `v2_server_vmess` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_stat`; DROP TABLE IF EXISTS `v2_stat_order`;
CREATE TABLE `v2_stat` ( CREATE TABLE `v2_stat_order` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`record_at` int(11) NOT NULL, `order_count` int(11) NOT NULL COMMENT '订单数量',
`record_type` char(1) NOT NULL, `order_amount` int(11) NOT NULL COMMENT '订单合计',
`order_count` int(11) NOT NULL COMMENT '订单数量', `commission_count` int(11) NOT NULL,
`order_total` int(11) NOT NULL COMMENT '订单合计', `commission_amount` int(11) NOT NULL COMMENT '佣金合计',
`commission_count` int(11) NOT NULL, `record_type` char(1) NOT NULL,
`commission_total` int(11) NOT NULL COMMENT '佣金合计', `record_at` int(11) NOT NULL,
`paid_count` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`paid_total` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
`register_count` int(11) NOT NULL, PRIMARY KEY (`id`),
`invite_count` int(11) NOT NULL, UNIQUE KEY `record_at` (`record_at`)
`transfer_used_total` varchar(32) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `record_at` (`record_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单统计'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单统计';
@ -425,8 +378,8 @@ CREATE TABLE `v2_user` (
`transfer_enable` bigint(20) NOT NULL DEFAULT '0', `transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`banned` tinyint(1) NOT NULL DEFAULT '0', `banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0', `is_admin` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`is_staff` tinyint(1) NOT NULL DEFAULT '0', `is_staff` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`last_login_ip` int(11) DEFAULT NULL, `last_login_ip` int(11) DEFAULT NULL,
`uuid` varchar(36) NOT NULL, `uuid` varchar(36) NOT NULL,
`group_id` int(11) DEFAULT NULL, `group_id` int(11) DEFAULT NULL,
@ -435,8 +388,8 @@ CREATE TABLE `v2_user` (
`remind_expire` tinyint(4) DEFAULT '1', `remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1', `remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL, `token` char(32) NOT NULL,
`expired_at` bigint(20) DEFAULT '0',
`remarks` text, `remarks` text,
`expired_at` bigint(20) DEFAULT '0',
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
@ -444,4 +397,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2023-05-23 17:01:12 -- 2022-11-27 07:09:04

View File

@ -107,6 +107,17 @@ CREATE TABLE `v2_coupon` (
ALTER TABLE `v2_order` ALTER TABLE `v2_order`
ADD `discount_amount` int(11) NULL AFTER `total_amount`; ADD `discount_amount` int(11) NULL AFTER `total_amount`;
CREATE TABLE `v2_tutorial` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`title` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`description` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`icon` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL,
`steps` text NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
);
ALTER TABLE `v2_server_log` ALTER TABLE `v2_server_log`
CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`; CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
@ -364,6 +375,20 @@ ALTER TABLE `v2_stat_server`
ADD INDEX `record_at` (`record_at`), ADD INDEX `record_at` (`record_at`),
ADD INDEX `server_id` (`server_id`); ADD INDEX `server_id` (`server_id`);
CREATE TABLE `v2_stat_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_count` int(11) NOT NULL COMMENT '订单数量',
`order_amount` int(11) NOT NULL COMMENT '订单合计',
`commission_count` int(11) NOT NULL,
`commission_amount` int(11) NOT NULL COMMENT '佣金合计',
`record_type` char(1) NOT NULL,
`record_at` int(11) NOT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `record_at` (`record_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单统计';
ALTER TABLE `v2_user` ALTER TABLE `v2_user`
DROP `enable`; DROP `enable`;
@ -574,6 +599,9 @@ ALTER TABLE `v2_server_shadowsocks`
ALTER TABLE `v2_server_trojan` ALTER TABLE `v2_server_trojan`
CHANGE `port` `port` varchar(11) NOT NULL COMMENT '连接端口' AFTER `host`; CHANGE `port` `port` varchar(11) NOT NULL COMMENT '连接端口' AFTER `host`;
DELETE FROM `v2_stat_server`
WHERE `server_type` = 'vmess';
ALTER TABLE `v2_server_shadowsocks` ALTER TABLE `v2_server_shadowsocks`
ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`; ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`;
@ -611,76 +639,3 @@ CREATE TABLE `v2_server_route` (
`updated_at` int(11) NOT NULL, `updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `v2_server_route`
CHANGE `match` `match` text COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `remarks`;
ALTER TABLE `v2_order`
ADD UNIQUE `trade_no` (`trade_no`);
ALTER TABLE `v2_plan`
CHANGE `content` `content` text COLLATE 'utf8mb4_general_ci' NULL AFTER `renew`;
ALTER TABLE `v2_plan`
COLLATE 'utf8mb4_general_ci';
ALTER TABLE `v2_server_v2ray`
RENAME TO `v2_server_vmess`;
ALTER TABLE `v2_server_vmess`
CHANGE `network` `network` varchar(11) COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `rate`;
DROP TABLE IF EXISTS `v2_server_hysteria`;
CREATE TABLE `v2_server_hysteria` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`route_id` varchar(255) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` varchar(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tags` varchar(255) DEFAULT NULL,
`rate` varchar(11) NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`up_mbps` int(11) NOT NULL,
`down_mbps` int(11) NOT NULL,
`server_name` varchar(64) DEFAULT NULL,
`insecure` tinyint(1) NOT NULL DEFAULT '0',
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `v2_plan`
ADD `capacity_limit` int(11) NULL AFTER `reset_traffic_method`;
ALTER TABLE `v2_stat_order`
CHANGE `record_at` `record_at` int(11) NOT NULL AFTER `id`,
CHANGE `record_type` `record_type` char(1) COLLATE 'utf8_general_ci' NOT NULL AFTER `record_at`,
CHANGE `order_count` `paid_count` int(11) NOT NULL COMMENT '订单数量' AFTER `record_type`,
CHANGE `order_amount` `paid_total` int(11) NOT NULL COMMENT '订单合计' AFTER `paid_count`,
CHANGE `commission_count` `commission_count` int(11) NOT NULL AFTER `paid_total`,
CHANGE `commission_amount` `commission_total` int(11) NOT NULL COMMENT '佣金合计' AFTER `commission_count`,
ADD `order_count` int(11) NOT NULL AFTER `record_type`,
ADD `order_total` int(11) NOT NULL AFTER `order_count`,
ADD `register_count` int(11) NOT NULL AFTER `order_total`,
ADD `invite_count` int(11) NOT NULL AFTER `register_count`,
ADD `transfer_used_total` varchar(32) NOT NULL AFTER `invite_count`,
RENAME TO `v2_stat`;
CREATE TABLE `v2_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`level` varchar(11) DEFAULT NULL,
`host` varchar(255) DEFAULT NULL,
`uri` varchar(255) NOT NULL,
`method` varchar(11) NOT NULL,
`data` text,
`ip` varchar(128) DEFAULT NULL,
`context` text,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

14
library/V2ray.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Library;
class V2ray
{
protected $config;
public function __construct()
{
$this->config = new \StdClass();
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ window.settings.i18n['ja-JP'] = {
'一次性': '一括払い', '一次性': '一括払い',
'重置流量包': '使用済みデータをリセット', '重置流量包': '使用済みデータをリセット',
'待支付': 'お支払い待ち', '待支付': 'お支払い待ち',
'开通中': '開通中', '开通中': '処理中',
'已取消': 'キャンセル済み', '已取消': 'キャンセル済み',
'已完成': '済み', '已完成': '済み',
'已折抵': '控除済み', '已折抵': '控除済み',
@ -80,7 +80,7 @@ window.settings.i18n['ja-JP'] = {
'总计': '合計金額', '总计': '合計金額',
'结账': 'チェックアウト', '结账': 'チェックアウト',
'等待支付中': 'お支払い待ち', '等待支付中': 'お支払い待ち',
'开通中': '開通中', '开通中': '処理中',
'订单系统正在进行处理请稍等1-3分钟。': 'システム処理中です、しばらくお待ちください', '订单系统正在进行处理请稍等1-3分钟。': 'システム処理中です、しばらくお待ちください',
'已取消': 'キャンセル済み', '已取消': 'キャンセル済み',
'订单由于超时支付已被取消。': 'ご注文はキャンセルされました', '订单由于超时支付已被取消。': 'ご注文はキャンセルされました',
@ -118,8 +118,8 @@ window.settings.i18n['ja-JP'] = {
'立即开始': '今すぐ連携開始', '立即开始': '今すぐ連携開始',
'重置订阅信息': 'サブスクリプションURLの変更', '重置订阅信息': 'サブスクリプションURLの変更',
'重置': '変更', '重置': '変更',
'确定要重置订阅信息?': 'サブスクリプションURLをご変更なされますか?', '确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか?',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります', '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
'重置成功': '変更完了', '重置成功': '変更完了',
'两次新密码输入不同': 'ご入力されました新しいパスワードが一致しません', '两次新密码输入不同': 'ご入力されました新しいパスワードが一致しません',
'两次密码输入不同': 'ご入力されましたパスワードが一致しません', '两次密码输入不同': 'ご入力されましたパスワードが一致しません',
@ -150,8 +150,8 @@ window.settings.i18n['ja-JP'] = {
'确定重置当前已用流量?': '利用済みデータ量をリセットしますか?', '确定重置当前已用流量?': '利用済みデータ量をリセットしますか?',
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '「確定」をクリックし次のページへ移動,お支払い後に当月分のデータ通信量は即時リセットされます', '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '「確定」をクリックし次のページへ移動,お支払い後に当月分のデータ通信量は即時リセットされます',
'确定': '確定', '确定': '確定',
'确定要重置订阅信息?': 'サブスクリプションURLをご変更なされますか?', '确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか?',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります', '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
'重置成功': '変更完了', '重置成功': '変更完了',
'低': '低', '低': '低',
'中': '中', '中': '中',
@ -167,7 +167,7 @@ window.settings.i18n['ja-JP'] = {
'关闭': '終了', '关闭': '終了',
'新的工单': '新規お問い合わせ', '新的工单': '新規お問い合わせ',
'新的工单': '新規お問い合わせ', '新的工单': '新規お問い合わせ',
'确认': 'OK', '确认': '送信',
'主题': 'タイトル', '主题': 'タイトル',
'请输入工单主题': 'お問い合わせタイトルをご入力ください', '请输入工单主题': 'お問い合わせタイトルをご入力ください',
'工单等级': 'ご希望のプライオリティ', '工单等级': 'ご希望のプライオリティ',
@ -185,7 +185,7 @@ window.settings.i18n['ja-JP'] = {
'一键订阅': 'ワンクリックインポート', '一键订阅': 'ワンクリックインポート',
'复制订阅': 'サブスクリプションのURLをコピー', '复制订阅': 'サブスクリプションのURLをコピー',
'推广佣金划转至余额': 'コミッションを残高へ振替', '推广佣金划转至余额': 'コミッションを残高へ振替',
'确认': 'OK', '确认': '送信',
'划转后的余额仅用于{title}消费使用': '振替済みの残高は{title}でのみご利用可能です', '划转后的余额仅用于{title}消费使用': '振替済みの残高は{title}でのみご利用可能です',
'当前推广佣金余额': '現在のコミッション金額', '当前推广佣金余额': '現在のコミッション金額',
'划转金额': '振替金額', '划转金额': '振替金額',
@ -234,7 +234,7 @@ window.settings.i18n['ja-JP'] = {
'已用流量将在 {reset_day} 日后重置': '利用済みデータ量は {reset_day} 日後にリセットします', '已用流量将在 {reset_day} 日后重置': '利用済みデータ量は {reset_day} 日後にリセットします',
'已用流量已在今日重置': '利用済みデータ量は本日リセットされました', '已用流量已在今日重置': '利用済みデータ量は本日リセットされました',
'重置已用流量': '利用済みデータ量をリセット', '重置已用流量': '利用済みデータ量をリセット',
'查看节点状态': '接続先サーバのステータス', '查看节点状态': '查看节点状态',
'当前已使用流量达{rate}%': 'データ使用量が{rate}%になりました', '当前已使用流量达{rate}%': 'データ使用量が{rate}%になりました',
'节点名称': 'サーバー名', '节点名称': 'サーバー名',
'于 {date} 到期,距离到期还有 {day} 天。': 'ご利用期限は {date} まで,期限まであと {day} 日', '于 {date} 到期,距离到期还有 {day} 天。': 'ご利用期限は {date} まで,期限まであと {day} 日',
@ -253,25 +253,25 @@ window.settings.i18n['ja-JP'] = {
'捷径': 'ショートカット', '捷径': 'ショートカット',
'不会使用,查看使用教程': 'ご利用方法がわからない方はナレッジベースをご閲覧ください', '不会使用,查看使用教程': 'ご利用方法がわからない方はナレッジベースをご閲覧ください',
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅', '使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
'扫描二维码订阅': 'QRコードをスキャンしてサブスクを設定', '扫描二维码订阅': '扫描二维码订阅',
'续费': '更新', '续费': '更新',
'购买': '購入', '购买': '購入',
'查看教程': 'チュートリアルを表示', '查看教程': 'チュートリアルを表示',
'注意': '注意', '注意': '注意',
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': 'まだ購入が完了していないオーダーがあります。購入前にそちらをキャンセルする必要がありますが、キャンセルしてよろしいですか', '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗',
'确定取消': 'キャンセル', '确定取消': '确定取消',
'返回我的订单': '注文履歴に戻る', '返回我的订单': '返回我的订单',
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': 'もし既にお支払いが完了していると、注文をキャンセルすると支払いが失敗となる可能性があります。キャンセルしてもよろしいですか', '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗',
'选择最适合你的计划': 'あなたにピッタリのプランをお選びください', '选择最适合你的计划': '选择最适合你的计划',
'全部': '全', '全部': '全',
'按周期': '期間順', '按周期': '周期別',
'遇到问题': '何かお困りですか?', '遇到问题': '遇到问题',
'遇到问题可以通过工单与我们沟通': '何かお困りでしたら、お問い合わせからご連絡ください。', '遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通',
'按流量': 'データ通信量順', '按流量': '按流量',
'搜索文档': 'ドキュメント内を検索', '搜索文档': '搜索文档',
'技术支持': 'テクニカルサポート', '技术支持': '技术支持',
'当前剩余佣金': 'コミッション残高', '当前剩余佣金': '当前剩余佣金',
'三级分销比例': '3ティア比率', '三级分销比例': '三级分销比例',
'累计获得佣金': '累計獲得コミッション金額', '累计获得佣金': '累计获得佣金',
'您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': 'お客様に招待された方が更に別の方を招待された場合、お客様は支払われるオーダーからティア分配分の比率分を受け取ることができます。' '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': '您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。'
}; };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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