Merge pull request #848 from v2board/dev

1.7.4
This commit is contained in:
tokumeikoi 2023-06-04 01:47:49 +08:00 committed by GitHub
commit 0ca47622a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 1352 additions and 405 deletions

View File

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

View File

@ -50,7 +50,7 @@ class CheckCommission extends Command
if ((int)config('v2board.commission_auto_check_enable', 1)) {
Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->whereNotIn('status', [0, 2])
->where('status', 3)
->where('updated_at', '<=', strtotime('-3 day', time()))
->update([
'commission_status' => 1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\StatUser;
use App\Services\ServerService;
use App\Services\StatisticalService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerGroup;
@ -15,16 +16,85 @@ use App\Models\Plan;
use App\Models\User;
use App\Models\Ticket;
use App\Models\Order;
use App\Models\StatOrder;
use App\Models\Stat;
use App\Models\StatServer;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
public function getStat(Request $request)
{
$params = $request->validate([
'start_at' => '',
'end_at' => ''
]);
if (isset($params['start_at']) && isset($params['end_at'])) {
$stats = Stat::where('record_at', '>=', $params['start_at'])
->where('record_at', '<', $params['end_at'])
->get()
->makeHidden(['record_at', 'created_at', 'updated_at', 'id', 'record_type'])
->toArray();
} else {
$statisticalService = new StatisticalService();
return [
'data' => $statisticalService->generateStatData()
];
}
$stats = array_reduce($stats, function($carry, $item) {
foreach($item as $key => $value) {
if(isset($carry[$key]) && $carry[$key]) {
$carry[$key] += $value;
} else {
$carry[$key] = $value;
}
}
return $carry;
}, []);
return [
'data' => $stats
];
}
public function getStatRecord(Request $request)
{
$request->validate([
'type' => 'required|in:paid_total,commission_total,register_count',
'start_at' => '',
'end_at' => ''
]);
$statisticalService = new StatisticalService();
$statisticalService->setStartAt($request->input('start_at'));
$statisticalService->setEndAt($request->input('end_at'));
return [
'data' => $statisticalService->getStatRecord($request->input('type'))
];
}
public function getRanking(Request $request)
{
$request->validate([
'type' => 'required|in:server_traffic_rank,user_consumption_rank,invite_rank',
'start_at' => '',
'end_at' => '',
'limit' => 'nullable|integer'
]);
$statisticalService = new StatisticalService();
$statisticalService->setStartAt($request->input('start_at'));
$statisticalService->setEndAt($request->input('end_at'));
return [
'data' => $statisticalService->getRanking($request->input('type'), $request->input('limit') ?? 20)
];
}
public function getOverride(Request $request)
{
return response([
return [
'data' => [
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
@ -55,12 +125,12 @@ class StatController extends Controller
->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('get_amount'),
]
]);
];
}
public function getOrder(Request $request)
{
$statistics = StatOrder::where('record_type', 'd')
$statistics = Stat::where('record_type', 'd')
->limit(31)
->orderBy('record_at', 'DESC')
->get()
@ -68,31 +138,31 @@ class StatController extends Controller
$result = [];
foreach ($statistics as $statistic) {
$date = date('m-d', $statistic['record_at']);
array_push($result, [
$result[] = [
'type' => '收款金额',
'date' => $date,
'value' => $statistic['order_amount'] / 100
]);
array_push($result, [
'value' => $statistic['paid_total'] / 100
];
$result[] = [
'type' => '收款笔数',
'date' => $date,
'value' => $statistic['order_count']
]);
array_push($result, [
'value' => $statistic['paid_count']
];
$result[] = [
'type' => '佣金金额(已发放)',
'date' => $date,
'value' => $statistic['commission_amount'] / 100
]);
array_push($result, [
'value' => $statistic['commission_total'] / 100
];
$result[] = [
'type' => '佣金笔数(已发放)',
'date' => $date,
'value' => $statistic['commission_count']
]);
];
}
$result = array_reverse($result);
return response([
return [
'data' => $result
]);
];
}
public function getServerLastRank()
@ -106,12 +176,12 @@ class StatController extends Controller
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$endAt = strtotime(date('Y-m-d'));
$statistics = StatServer::select([
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
->where('record_at', '>=', $startAt)
->where('record_at', '<', $endAt)
->where('record_type', 'd')
@ -128,9 +198,9 @@ class StatController extends Controller
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
}
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return response([
return [
'data' => $statistics
]);
];
}
public function getStatUser(Request $request)
@ -150,5 +220,6 @@ class StatController extends Controller
'total' => $total
];
}
}

View File

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

View File

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

View File

@ -34,7 +34,6 @@ class Clash
} else {
$config = Yaml::parseFile($defaultConfig);
}
$this->patch($config);
$proxy = [];
$proxies = [];
@ -78,6 +77,11 @@ class Clash
if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
return $group['proxies'];
});
$config['proxy-groups'] = array_values($config['proxy-groups']);
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST'];
if ($subsDomain) {
@ -124,6 +128,11 @@ class Clash
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {
@ -174,11 +183,4 @@ class Clash
{
return @preg_match($exp, null) !== false;
}
private function patch(&$config)
{
// fix clash x dns mode
preg_match('#(ClashX)[/ ]([0-9.]*)#', $_SERVER['HTTP_USER_AGENT'], $matches);
if (isset($matches[2]) && $matches[2] < '1.96.2') $config['dns']['enhanced-mode'] = 'redir-host';
}
}

View File

@ -7,7 +7,7 @@ use Symfony\Component\Yaml\Yaml;
class ClashMeta
{
public $flag = 'clashmeta';
public $flag = 'meta';
private $servers;
private $user;
@ -68,6 +68,10 @@ class ClashMeta
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) {
@ -82,12 +86,12 @@ class ClashMeta
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
$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::getShadowsocksServerKey($server['created_at'], 32);
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
@ -124,6 +128,11 @@ class ClashMeta
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {

View File

@ -0,0 +1,113 @@
<?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

@ -0,0 +1,137 @@
<?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

@ -68,6 +68,11 @@ class Passwall
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -72,6 +72,11 @@ class SagerNet
$config['sni'] = urlencode($tlsSettings['serverName']);
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -46,12 +46,12 @@ class Shadowrocket
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
$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::getShadowsocksServerKey($server['created_at'], 32);
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
@ -82,6 +82,15 @@ class Shadowrocket
$config['peer'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
if ($server['networkSettings']) {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type']) && !empty($tcpSettings['header']['type']))
$config['obfs'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0]) && !empty($tcpSettings['header']['request']['path'][0]))
$config['path'] = $tcpSettings['header']['request']['path'][0];
}
}
if ($server['network'] === 'ws') {
$config['obfs'] = "websocket";
if ($server['networkSettings']) {

View File

@ -75,6 +75,10 @@ class Stash
if ($isFilter) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
$config['proxy-groups'] = array_filter($config['proxy-groups'], function($group) {
return $group['proxies'];
});
$config['proxy-groups'] = array_values($config['proxy-groups']);
// Force the current subscription domain to be a direct rule
$subsDomain = $_SERVER['HTTP_HOST'];
if ($subsDomain) {
@ -121,6 +125,11 @@ class Stash
$array['servername'] = $tlsSettings['serverName'];
}
}
if ($server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $array['network'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $array['http-opts']['path'] = $tcpSettings['header']['request']['path'][0];
}
if ($server['network'] === 'ws') {
$array['network'] = 'ws';
if ($server['networkSettings']) {

View File

@ -40,12 +40,12 @@ class V2rayN
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
$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::getShadowsocksServerKey($server['created_at'], 32);
$serverKey = Helper::getServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
@ -80,6 +80,11 @@ class V2rayN
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -68,6 +68,11 @@ class V2rayNG
$config['sni'] = $tlsSettings['serverName'];
}
}
if ((string)$server['network'] === 'tcp') {
$tcpSettings = $server['networkSettings'];
if (isset($tcpSettings['header']['type'])) $config['type'] = $tcpSettings['header']['type'];
if (isset($tcpSettings['header']['request']['path'][0])) $config['path'] = $tcpSettings['header']['request']['path'][0];
}
if ((string)$server['network'] === 'ws') {
$wsSettings = $server['networkSettings'];
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];

View File

@ -116,7 +116,7 @@ class AuthController extends Controller
if (empty($request->input('email_code'))) {
abort(500, __('Email verification code cannot be empty'));
}
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
abort(500, __('Incorrect email verification code'));
}
}
@ -286,7 +286,7 @@ class AuthController extends Controller
public function forget(AuthForget $request)
{
if (Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== $request->input('email_code')) {
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
abort(500, __('Incorrect email verification code'));
}
$user = User::where('email', $request->input('email'))->first();

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
@ -81,11 +82,12 @@ class DeepbworkController extends Controller
Cache::put(CacheKey::get('SERVER_VMESS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_VMESS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'vmess');
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'vmess', $formatData);
return response([
'ret' => 1,

View File

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

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
@ -78,11 +79,11 @@ class TrojanTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'trojan');
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'trojan', $formatData);
return response([
'ret' => 1,

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\StatisticalService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
@ -63,11 +64,7 @@ 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) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
$userService = new UserService();
foreach (array_keys($data) as $k) {
$u = $data[$k][0];
$d = $data[$k][1];
$userService->trafficFetch($u, $d, $k, $this->nodeInfo->toArray(), $this->nodeType);
}
$userService->trafficFetch($this->nodeInfo->toArray(), $this->nodeType, $data);
return response([
'data' => true
@ -87,10 +84,10 @@ class UniProxyController extends Controller
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 16);
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 32);
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 32);
}
break;
case 'vmess':
@ -105,7 +102,17 @@ class UniProxyController extends Controller
$response = [
'host' => $this->nodeInfo->host,
'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;
}

View File

@ -191,6 +191,7 @@ class OrderController extends Controller
$payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
}

View File

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

View File

@ -0,0 +1,24 @@
<?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

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

View File

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

View File

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

View File

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

View File

@ -1,78 +0,0 @@
<?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, '节点统计数据创建失败');
}
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,11 @@
<?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

@ -0,0 +1,44 @@
<?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

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

19
app/Models/ServerHysteria.php Executable file
View File

@ -0,0 +1,19 @@
<?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'
];
}

16
app/Models/Stat.php Normal file
View File

@ -0,0 +1,16 @@
<?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

@ -33,6 +33,11 @@ class StripeCheckout {
'label' => 'WebHook 密钥签名',
'description' => '',
'type' => 'input',
],
'stripe_custom_field_name' => [
'label' => '自定义字段名称',
'description' => '例如可设置为“联系方式”,以便及时与客户取得联系',
'type' => 'input',
]
];
}
@ -44,6 +49,7 @@ class StripeCheckout {
if (!$exchange) {
abort(500, __('Currency conversion has timed out, please try again later'));
}
$customFieldName = isset($this->config['stripe_custom_field_name']) ? $this->config['stripe_custom_field_name'] : 'Contact Infomation';
$params = [
'success_url' => $order['return_url'],
@ -61,7 +67,16 @@ class StripeCheckout {
'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
];

View File

@ -223,7 +223,11 @@ class OrderService
$order->paid_at = time();
$order->callback_no = $callbackNo;
if (!$order->save()) return false;
OrderHandleJob::dispatch($order->trade_no);
try {
OrderHandleJob::dispatchNow($order->trade_no);
} catch (\Exception $e) {
return false;
}
return true;
}

View File

@ -2,6 +2,7 @@
namespace App\Services;
use App\Models\ServerHysteria;
use App\Models\ServerLog;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
@ -61,6 +62,29 @@ class ServerService
return $servers;
}
public function getAvailableHysteria(User $user)
{
$availableServers = [];
$model = ServerHysteria::orderBy('sort', 'ASC');
$servers = $model->get()->keyBy('id');
foreach ($servers as $key => $v) {
if (!$v['show']) continue;
$servers[$key]['type'] = 'hysteria';
$servers[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_HYSTERIA_LAST_CHECK_AT', $v['id']));
if (!in_array($user->group_id, $v['group_id'])) continue;
if (strpos($v['port'], '-') !== false) {
$servers[$key]['port'] = Helper::randomPort($v['port']);
}
if (isset($servers[$v['parent_id']])) {
$servers[$key]['last_check_at'] = Cache::get(CacheKey::get('SERVER_HYSTERIA_LAST_CHECK_AT', $v['parent_id']));
$servers[$key]['created_at'] = $servers[$v['parent_id']]['created_at'];
}
$servers[$key]['server_key'] = Helper::getServerKey($servers[$key]['created_at'], 16);
$availableServers[] = $servers[$key]->toArray();
}
return $availableServers;
}
public function getAvailableShadowsocks(User $user)
{
$servers = [];
@ -88,7 +112,8 @@ class ServerService
$servers = array_merge(
$this->getAvailableShadowsocks($user),
$this->getAvailableVmess($user),
$this->getAvailableTrojan($user)
$this->getAvailableTrojan($user),
$this->getAvailableHysteria($user)
);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
@ -182,6 +207,17 @@ class ServerService
return $servers;
}
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)
{
foreach ($servers as $k => $v) {
@ -204,7 +240,8 @@ class ServerService
$servers = array_merge(
$this->getAllShadowsocks(),
$this->getAllVMess(),
$this->getAllTrojan()
$this->getAllTrojan(),
$this->getAllHysteria()
);
$this->mergeData($servers);
$tmp = array_column($servers, 'sort');
@ -233,6 +270,8 @@ class ServerService
return ServerShadowsocks::find($serverId);
case 'trojan':
return ServerTrojan::find($serverId);
case 'hysteria':
return ServerHysteria::find($serverId);
default:
return false;
}

View File

@ -0,0 +1,283 @@
<?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

@ -168,10 +168,18 @@ class UserService
return true;
}
public function trafficFetch(int $u, int $d, int $userId, array $server, string $protocol)
public function trafficFetch(array $server, string $protocol, array $data)
{
TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
StatServerJob::dispatch($u, $d, $server, $protocol, 'd');
StatUserJob::dispatch($u, $d, $userId, $server, $protocol, 'd');
$statService = new StatisticalService();
$statService->setStartAt(strtotime(date('Y-m-d')));
$statService->setUserStats();
$statService->setServerStats();
foreach (array_keys($data) as $userId) {
$u = $data[$userId][0];
$d = $data[$userId][1];
TrafficFetchJob::dispatch($u, $d, $userId, $server, $protocol);
$statService->statServer($server['id'], $protocol, $u, $d);
$statService->statUser($server['rate'], $userId, $u, $d);
}
}
}

View File

@ -16,6 +16,9 @@ class CacheKey
'SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点在线用户',
'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间',
'SERVER_HYSTERIA_ONLINE_USER' => 'hysteria节点在线用户',
'SERVER_HYSTERIA_LAST_CHECK_AT' => 'hysteria节点最后检查时间',
'SERVER_HYSTERIA_LAST_PUSH_AT' => 'hysteria节点最后推送时间',
'TEMP_TOKEN' => '临时令牌',
'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',

View File

@ -9,7 +9,7 @@ class Helper
return base64_encode(substr($uuid, 0, $length));
}
public static function getShadowsocksServerKey($timestamp, $length)
public static function getServerKey($timestamp, $length)
{
return base64_encode(substr(md5($timestamp), 0, $length));
}

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
-- Adminer 4.8.1 MySQL 5.7.29 dump
-- Adminer 4.7.7 MySQL dump
SET NAMES utf8;
SET time_zone = '+00:00';
@ -81,6 +81,23 @@ CREATE TABLE `v2_knowledge` (
) 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`;
CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -163,8 +180,8 @@ CREATE TABLE `v2_plan` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`transfer_enable` int(11) NOT NULL,
`speed_limit` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`speed_limit` int(11) DEFAULT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`renew` tinyint(1) NOT NULL DEFAULT '1',
@ -195,6 +212,30 @@ CREATE TABLE `v2_server_group` (
) 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`;
CREATE TABLE `v2_server_route` (
`id` int(11) NOT NULL AUTO_INCREMENT,
@ -280,19 +321,24 @@ CREATE TABLE `v2_server_vmess` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_stat_order`;
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`)
DROP TABLE IF EXISTS `v2_stat`;
CREATE TABLE `v2_stat` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`record_at` int(11) NOT NULL,
`record_type` char(1) NOT NULL,
`order_count` int(11) NOT NULL COMMENT '订单数量',
`order_total` int(11) NOT NULL COMMENT '订单合计',
`commission_count` int(11) NOT NULL,
`commission_total` int(11) NOT NULL COMMENT '佣金合计',
`paid_count` int(11) NOT NULL,
`paid_total` int(11) NOT NULL,
`register_count` int(11) NOT NULL,
`invite_count` int(11) NOT NULL,
`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='订单统计';
@ -379,8 +425,8 @@ CREATE TABLE `v2_user` (
`transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0',
`is_staff` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL,
`is_staff` tinyint(1) NOT NULL DEFAULT '0',
`last_login_ip` int(11) DEFAULT NULL,
`uuid` varchar(36) NOT NULL,
`group_id` int(11) DEFAULT NULL,
@ -389,8 +435,8 @@ CREATE TABLE `v2_user` (
`remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL,
`remarks` text,
`expired_at` bigint(20) DEFAULT '0',
`remarks` text,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`),
@ -398,4 +444,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2023-03-07 13:10:15
-- 2023-05-23 17:01:12

View File

@ -107,17 +107,6 @@ CREATE TABLE `v2_coupon` (
ALTER TABLE `v2_order`
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`
CHANGE `rate` `rate` decimal(10,2) NOT NULL AFTER `d`;
@ -375,20 +364,6 @@ ALTER TABLE `v2_stat_server`
ADD INDEX `record_at` (`record_at`),
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`
DROP `enable`;
@ -599,9 +574,6 @@ ALTER TABLE `v2_server_shadowsocks`
ALTER TABLE `v2_server_trojan`
CHANGE `port` `port` varchar(11) NOT NULL COMMENT '连接端口' AFTER `host`;
DELETE FROM `v2_stat_server`
WHERE `server_type` = 'vmess';
ALTER TABLE `v2_server_shadowsocks`
ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`;
@ -657,3 +629,58 @@ ALTER TABLE `v2_server_v2ray`
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;

View File

@ -1,14 +0,0 @@
<?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分钟。': 'システム処理中です、しばらくお待ちください',
'已取消': 'キャンセル済み',
'订单由于超时支付已被取消。': 'ご注文はキャンセルされました',
@ -118,8 +118,8 @@ window.settings.i18n['ja-JP'] = {
'立即开始': '今すぐ連携開始',
'重置订阅信息': 'サブスクリプションURLの変更',
'重置': '変更',
'确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか?',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
'确定要重置订阅信息?': 'サブスクリプションURLをご変更なされますか?',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
'重置成功': '変更完了',
'两次新密码输入不同': 'ご入力されました新しいパスワードが一致しません',
'两次密码输入不同': 'ご入力されましたパスワードが一致しません',
@ -150,8 +150,8 @@ window.settings.i18n['ja-JP'] = {
'确定重置当前已用流量?': '利用済みデータ量をリセットしますか?',
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '「確定」をクリックし次のページへ移動,お支払い後に当月分のデータ通信量は即時リセットされます',
'确定': '確定',
'确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか?',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'サブスクリプションのURL及び情報が外部に漏れた場合にご操作ください。操作後はUUIDやURLが変更され、再度サブスクリプションのインポートが必要になります',
'确定要重置订阅信息?': 'サブスクリプション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をコピー',
'推广佣金划转至余额': 'コミッションを残高へ振替',
'确认': '送信',
'确认': 'OK',
'划转后的余额仅用于{title}消费使用': '振替済みの残高は{title}でのみご利用可能です',
'当前推广佣金余额': '現在のコミッション金額',
'划转金额': '振替金額',
@ -234,7 +234,7 @@ window.settings.i18n['ja-JP'] = {
'已用流量将在 {reset_day} 日后重置': '利用済みデータ量は {reset_day} 日後にリセットします',
'已用流量已在今日重置': '利用済みデータ量は本日リセットされました',
'重置已用流量': '利用済みデータ量をリセット',
'查看节点状态': '查看节点状态',
'查看节点状态': '接続先サーバのステータス',
'当前已使用流量达{rate}%': 'データ使用量が{rate}%になりました',
'节点名称': 'サーバー名',
'于 {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

View File

@ -23,7 +23,7 @@
<script>
window.settings = {
title: '{{$title}}',
theme_path: '{{$theme_path}}',
assets_path: '/theme/{{$theme}}/assets',
theme: {
sidebar: '{{$theme_config['theme_sidebar']}}',
header: '{{$theme_config['theme_header']}}',

View File

@ -0,0 +1,5 @@
@extends('errors::minimal')
@section('title', __('Server Error'))
@section('code', '500')
@section('message', __($exception->getMessage() ?: 'Server Error'))

View File

@ -23,7 +23,6 @@ Route::get('/', function (Request $request) {
$renderParams = [
'title' => config('v2board.app_name', 'V2Board'),
'theme' => config('v2board.frontend_theme', 'v2board'),
'theme_path' => '/theme/' . config('v2board.frontend_theme', 'v2board') . '/assets/',
'version' => config('app.version'),
'description' => config('v2board.app_description', 'V2Board is best'),
'logo' => config('v2board.logo')

View File

@ -1,5 +1,16 @@
#!/bin/bash
if [ ! -d ".git" ]; then
echo "Please deploy using Git."
exit 1
fi
if ! command -v git &> /dev/null; then
echo "Git is not installed! Please install git and try again."
exit 1
fi
git config --global --add safe.directory $(pwd)
git fetch --all && git reset --hard origin/master && git pull origin master
rm -rf composer.lock composer.phar
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar

View File

@ -1,5 +1,16 @@
#!/bin/bash
if [ ! -d ".git" ]; then
echo "Please deploy using Git."
exit 1
fi
if ! command -v git &> /dev/null; then
echo "Git is not installed! Please install git and try again."
exit 1
fi
git config --global --add safe.directory $(pwd)
git fetch --all && git reset --hard origin/dev && git pull origin dev
git checkout dev
rm -rf composer.lock composer.phar