mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-01 23:18:13 -05:00
Compare commits
5 Commits
3efdeaa9c9
...
6b436255a0
Author | SHA1 | Date | |
---|---|---|---|
|
6b436255a0 | ||
|
e1db186ee7 | ||
|
720aa415ac | ||
|
cd7c1ca34b | ||
|
652c6eb125 |
@ -1,15 +1,6 @@
|
||||
<?php
|
||||
use App\Support\Setting;
|
||||
|
||||
|
||||
if (! function_exists("get_request_content")){
|
||||
function get_request_content(){
|
||||
|
||||
return request()->getContent() ?: json_encode($_POST);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('admin_setting')) {
|
||||
/**
|
||||
* 获取或保存配置参数.
|
||||
|
@ -23,7 +23,7 @@ class TelegramController extends Controller
|
||||
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
|
||||
throw new ApiException('access_token is error', 401);
|
||||
}
|
||||
$data = json_decode(get_request_content(),true);
|
||||
$data = json_decode(request()->getContent(),true);
|
||||
$this->formatMessage($data);
|
||||
$this->formatChatJoinRequest($data);
|
||||
$this->handle();
|
||||
|
@ -36,15 +36,34 @@ class UniProxyController extends Controller
|
||||
// 后端提交数据
|
||||
public function push(Request $request)
|
||||
{
|
||||
$res = json_decode(get_request_content(), true);
|
||||
$res = json_decode(request()->getContent(), true);
|
||||
if (!is_array($res)) {
|
||||
return $this->fail([422, 'Invalid data format']);
|
||||
}
|
||||
$data = array_filter($res, function ($item) {
|
||||
return is_array($item) && count($item) === 2 && is_numeric($item[0]) && is_numeric($item[1]);
|
||||
return is_array($item)
|
||||
&& count($item) === 2
|
||||
&& is_numeric($item[0])
|
||||
&& is_numeric($item[1]);
|
||||
});
|
||||
if (empty($data)) {
|
||||
return $this->success(true);
|
||||
}
|
||||
$node = $request->input('node_info');
|
||||
$nodeType = $node->type;
|
||||
$nodeId = $node->id;
|
||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId), count($data), 3600);
|
||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId), time(), 3600);
|
||||
|
||||
Cache::put(
|
||||
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId),
|
||||
count($data),
|
||||
3600
|
||||
);
|
||||
Cache::put(
|
||||
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId),
|
||||
time(),
|
||||
3600
|
||||
);
|
||||
|
||||
$userService = new UserService();
|
||||
$userService->trafficFetch($node->toArray(), $nodeType, $data);
|
||||
return $this->success(true);
|
||||
@ -55,48 +74,50 @@ class UniProxyController extends Controller
|
||||
{
|
||||
$node = $request->input('node_info');
|
||||
$nodeType = $node->type;
|
||||
|
||||
$protocolSettings = $node->protocol_settings;
|
||||
|
||||
$serverPort = $node->server_port;
|
||||
$host = $node->host;
|
||||
|
||||
$baseConfig = [
|
||||
'server_port' => $serverPort,
|
||||
'network' => $protocolSettings['network'] ?? null,
|
||||
'network_settings' => $protocolSettings['network_settings'] ?? null,
|
||||
];
|
||||
|
||||
$response = match ($nodeType) {
|
||||
'shadowsocks' => [
|
||||
'server_port' => $node->server_port,
|
||||
...$baseConfig,
|
||||
'cipher' => $protocolSettings['cipher'],
|
||||
'obfs' => $protocolSettings['obfs'],
|
||||
'obfs_settings' => $protocolSettings['obfs_settings'],
|
||||
'server_key' => $protocolSettings['cipher'] === '2022-blake3-aes-128-gcm'
|
||||
? Helper::getServerKey($node->created_at, 16)
|
||||
: ($protocolSettings['cipher'] === '2022-blake3-aes-256-gcm'
|
||||
? Helper::getServerKey($node->created_at, 32)
|
||||
: null)
|
||||
'server_key' => match ($protocolSettings['cipher']) {
|
||||
'2022-blake3-aes-128-gcm' => Helper::getServerKey($node->created_at, 16),
|
||||
'2022-blake3-aes-256-gcm' => Helper::getServerKey($node->created_at, 32),
|
||||
default => null
|
||||
}
|
||||
],
|
||||
'vmess' => [
|
||||
'server_port' => $node->server_port,
|
||||
'network' => $protocolSettings['network'],
|
||||
'networkSettings' => $protocolSettings['network_settings'],
|
||||
...$baseConfig,
|
||||
'tls' => $protocolSettings['tls']
|
||||
],
|
||||
'trojan' => [
|
||||
'host' => $node->host,
|
||||
'server_port' => $node->server_port,
|
||||
...$baseConfig,
|
||||
'host' => $host,
|
||||
'server_name' => $protocolSettings['server_name'],
|
||||
'network' => $protocolSettings['network'],
|
||||
'networkSettings' => $protocolSettings['network_settings'],
|
||||
],
|
||||
'vless' => [
|
||||
'server_port' => $node->server_port,
|
||||
'network' => $protocolSettings['network'],
|
||||
'network_settings' => $protocolSettings['network_settings'],
|
||||
...$baseConfig,
|
||||
'tls' => $protocolSettings['tls'],
|
||||
'flow' => $protocolSettings['flow'],
|
||||
'tls_settings' => match ((int) $protocolSettings['tls']) {
|
||||
1 => $protocolSettings['tls_settings'],
|
||||
2 => $protocolSettings['reality_settings']
|
||||
}
|
||||
'tls_settings' => (int) $protocolSettings['tls'] === 1
|
||||
? $protocolSettings['tls_settings']
|
||||
: $protocolSettings['reality_settings']
|
||||
],
|
||||
'hysteria' => [
|
||||
'version' => $protocolSettings['version'],
|
||||
'host' => $node->host,
|
||||
'server_port' => $node->server_port,
|
||||
'host' => $host,
|
||||
'server_port' => $serverPort,
|
||||
'server_name' => $protocolSettings['tls']['server_name'],
|
||||
'up_mbps' => $protocolSettings['bandwidth']['up'],
|
||||
'down_mbps' => $protocolSettings['bandwidth']['down'],
|
||||
@ -104,18 +125,20 @@ class UniProxyController extends Controller
|
||||
],
|
||||
default => []
|
||||
};
|
||||
|
||||
$response['base_config'] = [
|
||||
'push_interval' => (int) admin_setting('server_push_interval', 60),
|
||||
'pull_interval' => (int) admin_setting('server_pull_interval', 60)
|
||||
];
|
||||
if ($node['route_id']) {
|
||||
$response['routes'] = ServerService::getRoutes($node['route_id']);
|
||||
}
|
||||
$eTag = sha1(json_encode($response));
|
||||
if (strpos($request->header('If-None-Match'), $eTag) !== false) {
|
||||
return response(null, 304);
|
||||
|
||||
if (!empty($node['route_ids'])) {
|
||||
$response['routes'] = ServerService::getRoutes($node['route_ids']);
|
||||
}
|
||||
|
||||
$eTag = sha1(json_encode($response));
|
||||
if (strpos($request->header('If-None-Match') ?? '', $eTag) !== false) {
|
||||
return response(null, 304);
|
||||
}
|
||||
return response($response)->header('ETag', "\"{$eTag}\"");
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ class ManageController extends Controller
|
||||
{
|
||||
$servers = collect(ServerService::getAllServers())->map(function ($item) {
|
||||
$item['groups'] = ServerGroup::whereIn('id', $item['group_ids'])->get(['name', 'id']);
|
||||
$item['parent'] = $item->parent;
|
||||
return $item;
|
||||
});
|
||||
return $this->success($servers);
|
||||
|
@ -89,8 +89,8 @@ class StatController extends Controller
|
||||
'paid_count' => 0,
|
||||
'commission_total' => 0,
|
||||
'commission_count' => 0,
|
||||
'start_date' => $request->input('start_date', date('Y-m-d', $statistics->last()->record_at)),
|
||||
'end_date' => $request->input('end_date', date('Y-m-d', $statistics->first()->record_at)),
|
||||
'start_date' => $request->input('start_date', date('Y-m-d', $statistics->last()?->record_at)),
|
||||
'end_date' => $request->input('end_date', date('Y-m-d', $statistics->first()?->record_at)),
|
||||
'avg_paid_amount' => 0,
|
||||
'avg_commission_amount' => 0
|
||||
];
|
||||
|
@ -13,6 +13,7 @@ use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Services\AuthService;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@ -28,59 +29,128 @@ class UserController extends Controller
|
||||
return $this->success($user->save());
|
||||
}
|
||||
|
||||
private function applyFiltersAndSorts(Request $request, $builder)
|
||||
/**
|
||||
* Apply filters and sorts to the query builder
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
private function applyFiltersAndSorts(Request $request, Builder $builder): void
|
||||
{
|
||||
if ($request->has('filter')) {
|
||||
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||
$key = $filter['id'];
|
||||
$value = $filter['value'];
|
||||
$builder->where(function ($query) use ($key, $value) {
|
||||
if (is_array($value)) {
|
||||
if ($key === 'group_ids') {
|
||||
$query->where(function ($subQuery) use ($value) {
|
||||
$subQuery->whereIn('group_id', $value);
|
||||
});
|
||||
} else {
|
||||
$query->whereIn($key, $value);
|
||||
}
|
||||
} else {
|
||||
$query->where($key, 'like', "%{$value}%");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('sort')) {
|
||||
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||
$key = $sort['id'];
|
||||
$value = $sort['desc'] ? 'DESC' : 'ASC';
|
||||
$builder->orderBy($key, $value);
|
||||
});
|
||||
}
|
||||
$this->applyFilters($request, $builder);
|
||||
$this->applySorting($request, $builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply filters to the query builder
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
private function applyFilters(Request $request, Builder $builder): void
|
||||
{
|
||||
if (!$request->has('filter')) {
|
||||
return;
|
||||
}
|
||||
|
||||
collect($request->input('filter'))->each(function ($filter) use ($builder) {
|
||||
$field = $filter['id'];
|
||||
$value = $filter['value'];
|
||||
|
||||
$builder->where(function ($query) use ($field, $value) {
|
||||
$this->buildFilterQuery($query, $field, $value);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the filter query based on field and value
|
||||
*
|
||||
* @param Builder $query
|
||||
* @param string $field
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
private function buildFilterQuery(Builder $query, string $field, mixed $value): void
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
$query->where($field, 'like', "%{$value}%");
|
||||
return;
|
||||
}
|
||||
|
||||
if ($field === 'group_ids') {
|
||||
$query->whereIn('group_id', $value);
|
||||
return;
|
||||
}
|
||||
|
||||
$query->whereIn($field, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply sorting to the query builder
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Builder $builder
|
||||
* @return void
|
||||
*/
|
||||
private function applySorting(Request $request, Builder $builder): void
|
||||
{
|
||||
if (!$request->has('sort')) {
|
||||
return;
|
||||
}
|
||||
|
||||
collect($request->input('sort'))->each(function ($sort) use ($builder) {
|
||||
$field = $sort['id'];
|
||||
$direction = $sort['desc'] ? 'DESC' : 'ASC';
|
||||
$builder->orderBy($field, $direction);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch paginated user list with filters and sorting
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$current = $request->input('current', 1);
|
||||
$pageSize = $request->input('pageSize', 10);
|
||||
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])->select(
|
||||
DB::raw('*'),
|
||||
DB::raw('(u+d) as total_used')
|
||||
);
|
||||
|
||||
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])
|
||||
->select(DB::raw('*, (u+d) as total_used'));
|
||||
|
||||
$this->applyFiltersAndSorts($request, $userModel);
|
||||
$users = $userModel->orderBy('id', 'desc')->paginate($pageSize, ['*'], 'page', $current);
|
||||
|
||||
$users = $userModel->orderBy('id', 'desc')
|
||||
->paginate($pageSize, ['*'], 'page', $current);
|
||||
|
||||
$users->getCollection()->transform(function ($user) {
|
||||
$user->subscribe_url = Helper::getSubscribeUrl($user->token);
|
||||
$user->balance = $user->balance / 100;
|
||||
$user->commission_balance = $user->commission_balance / 100;
|
||||
return $user;
|
||||
return $this->transformUserData($user);
|
||||
});
|
||||
|
||||
return response([
|
||||
'data' => $users->items(),
|
||||
'total' => $users->total()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform user data for response
|
||||
*
|
||||
* @param User $user
|
||||
* @return User
|
||||
*/
|
||||
private function transformUserData(User $user): User
|
||||
{
|
||||
$user->subscribe_url = Helper::getSubscribeUrl($user->token);
|
||||
$user->balance = $user->balance / 100;
|
||||
$user->commission_balance = $user->commission_balance / 100;
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getUserInfoById(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
|
@ -93,9 +93,9 @@ class ServerSave extends FormRequest
|
||||
{
|
||||
return [
|
||||
'name.required' => '节点名称不能为空',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'group_id.array' => '权限组格式不正确',
|
||||
'route_id.array' => '路由组格式不正确',
|
||||
'group_ids.required' => '权限组不能为空',
|
||||
'group_ids.array' => '权限组格式不正确',
|
||||
'route_ids.array' => '路由组格式不正确',
|
||||
'parent_id.integer' => '父ID格式不正确',
|
||||
'host.required' => '节点地址不能为空',
|
||||
'port.required' => '连接端口不能为空',
|
||||
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerShadowsocksSave extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => '',
|
||||
'name' => 'required',
|
||||
'group_id' => 'required|array',
|
||||
'parent_id' => 'nullable|integer',
|
||||
'route_id' => 'nullable|array',
|
||||
'host' => 'required',
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'cipher' => 'required|in:aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm',
|
||||
'obfs' => 'nullable|in:http',
|
||||
'obfs_settings' => 'nullable|array',
|
||||
'tags' => 'nullable|array',
|
||||
'excludes' => 'nullable|array',
|
||||
'ips' => 'nullable|array',
|
||||
'rate' => 'required|numeric'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => '节点名称不能为空',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'group_id.array' => '权限组格式不正确',
|
||||
'route_id.array' => '路由组格式不正确',
|
||||
'parent_id.integer' => '父节点格式不正确',
|
||||
'host.required' => '节点地址不能为空',
|
||||
'port.required' => '连接端口不能为空',
|
||||
'server_port.required' => '后端服务端口不能为空',
|
||||
'cipher.required' => '加密方式不能为空',
|
||||
'tags.array' => '标签格式不正确',
|
||||
'rate.required' => '倍率不能为空',
|
||||
'rate.numeric' => '倍率格式不正确',
|
||||
'obfs.in' => '混淆格式不正确',
|
||||
'obfs_settings.array' => '混淆设置格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerShadowsocksUpdate extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => 'in:0,1'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'show.in' => '显示状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerTrojanSave extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => '',
|
||||
'name' => 'required',
|
||||
'network' => 'required',
|
||||
'networkSettings' => 'nullable',
|
||||
'group_id' => 'required|array',
|
||||
'route_id' => 'nullable|array',
|
||||
'parent_id' => 'nullable|integer',
|
||||
'host' => 'required',
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'allow_insecure' => 'nullable|in:0,1',
|
||||
'server_name' => 'nullable',
|
||||
'tags' => 'nullable|array',
|
||||
'excludes' => 'nullable|array',
|
||||
'ips' => 'nullable|array',
|
||||
'rate' => 'required|numeric'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => '节点名称不能为空',
|
||||
'network.required' => '传输协议不能为空',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'group_id.array' => '权限组格式不正确',
|
||||
'route_id.array' => '路由组格式不正确',
|
||||
'parent_id.integer' => '父节点格式不正确',
|
||||
'host.required' => '节点地址不能为空',
|
||||
'port.required' => '连接端口不能为空',
|
||||
'server_port.required' => '后端服务端口不能为空',
|
||||
'allow_insecure.in' => '允许不安全格式不正确',
|
||||
'tags.array' => '标签格式不正确',
|
||||
'rate.required' => '倍率不能为空',
|
||||
'rate.numeric' => '倍率格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerTrojanUpdate extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => 'in:0,1'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'show.in' => '显示状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerVmessSave extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => '',
|
||||
'name' => 'required',
|
||||
'group_id' => 'required|array',
|
||||
'route_id' => 'nullable|array',
|
||||
'parent_id' => 'nullable|integer',
|
||||
'host' => 'required',
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'tls' => 'required',
|
||||
'tags' => 'nullable|array',
|
||||
'excludes' => 'nullable|array',
|
||||
'ips' => 'nullable|array',
|
||||
'rate' => 'required|numeric',
|
||||
'network' => 'required|in:tcp,kcp,ws,http,domainsocket,quic,grpc',
|
||||
'networkSettings' => 'nullable|array',
|
||||
'ruleSettings' => 'nullable|array',
|
||||
'tlsSettings' => 'nullable|array',
|
||||
'dnsSettings' => 'nullable|array'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'name.required' => '节点名称不能为空',
|
||||
'group_id.required' => '权限组不能为空',
|
||||
'group_id.array' => '权限组格式不正确',
|
||||
'route_id.array' => '路由组格式不正确',
|
||||
'parent_id.integer' => '父ID格式不正确',
|
||||
'host.required' => '节点地址不能为空',
|
||||
'port.required' => '连接端口不能为空',
|
||||
'server_port.required' => '后端服务端口不能为空',
|
||||
'tls.required' => 'TLS不能为空',
|
||||
'tags.array' => '标签格式不正确',
|
||||
'rate.required' => '倍率不能为空',
|
||||
'rate.numeric' => '倍率格式不正确',
|
||||
'network.required' => '传输协议不能为空',
|
||||
'network.in' => '传输协议格式不正确',
|
||||
'networkSettings.array' => '传输协议配置有误',
|
||||
'ruleSettings.array' => '规则配置有误',
|
||||
'tlsSettings.array' => 'tls配置有误',
|
||||
'dnsSettings.array' => 'dns配置有误'
|
||||
];
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ServerVmessUpdate extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'show' => 'in:0,1'
|
||||
];
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [
|
||||
'show.in' => '显示状态格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
@ -27,9 +27,9 @@ class PlanResources extends JsonResource
|
||||
'capacity_limit' => $this->formatCapacityLimit(),
|
||||
'transfer_enable' => $this['transfer_enable'],
|
||||
'speed_limit' => $this['speed_limit'],
|
||||
'show' => (bool)$this['show'],
|
||||
'sell' => (bool)$this['sell'],
|
||||
'renew' => (bool)$this['renew'],
|
||||
'show' => (bool) $this['show'],
|
||||
'sell' => (bool) $this['sell'],
|
||||
'renew' => (bool) $this['renew'],
|
||||
'reset_traffic_method' => $this['reset_traffic_method'],
|
||||
'sort' => $this['sort'],
|
||||
'created_at' => $this['created_at'],
|
||||
@ -46,7 +46,7 @@ class PlanResources extends JsonResource
|
||||
{
|
||||
$prices = [];
|
||||
foreach (Plan::LEGACY_PERIOD_MAPPING as $legacyPeriod => $newPeriod) {
|
||||
$prices[$legacyPeriod] = optional($this['prices'])[$newPeriod] ? (int)$this['prices'][$newPeriod] * 100 : null;
|
||||
$prices[$legacyPeriod] = optional($this['prices'])[$newPeriod] ? (float) $this['prices'][$newPeriod] * 100 : null;
|
||||
}
|
||||
return $prices;
|
||||
}
|
||||
@ -66,6 +66,6 @@ class PlanResources extends JsonResource
|
||||
return __('Sold out');
|
||||
}
|
||||
|
||||
return (int)$this['capacity_limit'];
|
||||
return (int) $this['capacity_limit'];
|
||||
}
|
||||
}
|
||||
|
@ -185,11 +185,12 @@ class Server extends Model
|
||||
|
||||
$this->password = $user->uuid;
|
||||
|
||||
if (!isset($this->cipher) || !isset(self::CIPHER_CONFIGURATIONS[$this->cipher])) {
|
||||
$cipher = data_get($this, 'protocol_settings.cipher');
|
||||
if (!$cipher || !isset(self::CIPHER_CONFIGURATIONS[$cipher])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = self::CIPHER_CONFIGURATIONS[$this->cipher];
|
||||
$config = self::CIPHER_CONFIGURATIONS[$cipher];
|
||||
$serverKey = Helper::getServerKey($this->created_at, $config['serverKeySize']);
|
||||
$userKey = Helper::uuidToBase64($user->uuid, $config['userKeySize']);
|
||||
$this->password = "{$serverKey}:{$userKey}";
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ServerShadowsocks extends Model
|
||||
{
|
||||
protected $table = 'v2_server_shadowsocks';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'array',
|
||||
'route_id' => 'array',
|
||||
'tags' => 'array',
|
||||
'excludes' => 'array',
|
||||
'obfs_settings' => 'array',
|
||||
'ips' => 'array'
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
@ -47,4 +47,9 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ class BTCPay implements PaymentInterface
|
||||
|
||||
public function notify($params): array|bool
|
||||
{
|
||||
$payload = trim(get_request_content());
|
||||
$payload = trim(request()->getContent());
|
||||
|
||||
$headers = getallheaders();
|
||||
|
||||
|
@ -68,7 +68,7 @@ class Coinbase implements PaymentInterface
|
||||
public function notify($params): array
|
||||
{
|
||||
|
||||
$payload = trim(get_request_content());
|
||||
$payload = trim(request()->getContent());
|
||||
$json_param = json_decode($payload, true);
|
||||
|
||||
|
||||
|
@ -130,7 +130,7 @@ class ClashMeta implements ProtocolInterface
|
||||
$array['server'] = $server['host'];
|
||||
$array['port'] = $server['port'];
|
||||
$array['cipher'] = data_get($server['protocol_settings'], 'cipher');
|
||||
$array['password'] = $password;
|
||||
$array['password'] = data_get($server, 'password', $password);
|
||||
$array['udp'] = true;
|
||||
return $array;
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ class General implements ProtocolInterface
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$password = data_get($server, 'password', $password);
|
||||
$str = str_replace(
|
||||
['+', '/', '='],
|
||||
['-', '_', ''],
|
||||
|
@ -44,6 +44,7 @@ class QuantumultX implements ProtocolInterface
|
||||
public static function buildShadowsocks($password, $server)
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$password = data_get($server, 'password', $password);
|
||||
$config = [
|
||||
"shadowsocks={$server['host']}:{$server['port']}",
|
||||
"method={$protocol_settings['cipher']}",
|
||||
|
@ -60,6 +60,7 @@ class Shadowrocket implements ProtocolInterface
|
||||
{
|
||||
$protocol_settings = $server['protocol_settings'];
|
||||
$name = rawurlencode($server['name']);
|
||||
$password = data_get($server, 'password', $password);
|
||||
$str = str_replace(
|
||||
['+', '/', '='],
|
||||
['-', '_', ''],
|
||||
|
@ -107,7 +107,7 @@ class SingBox implements ProtocolInterface
|
||||
$array['server'] = $server['host'];
|
||||
$array['server_port'] = $server['port'];
|
||||
$array['method'] = data_get($server, 'protocol_settings.cipher');
|
||||
$array['password'] = $password;
|
||||
$array['password'] = data_get($server, 'password', $password);
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ class ServerService
|
||||
$server->loadParentCreatedAt();
|
||||
$server->handlePortAllocation();
|
||||
$server->loadServerStatus();
|
||||
$server->server_key = Helper::getServerKey($server->created_at, 16);
|
||||
if ($server->type === 'shadowsocks') {
|
||||
$server->server_key = Helper::getServerKey($server->created_at, 16);
|
||||
}
|
||||
$server->generateShadowsocksPassword($user);
|
||||
|
||||
return $server;
|
||||
@ -48,6 +50,10 @@ class ServerService
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加
|
||||
*/
|
||||
|
||||
/**
|
||||
* 根据权限组获取可用的用户列表
|
||||
* @param array $groupIds
|
||||
|
@ -2,20 +2,20 @@ services:
|
||||
web:
|
||||
image: ghcr.io/cedar2025/xboard:new
|
||||
volumes:
|
||||
- ./.docker/.data/redis/:/run/redis-socket
|
||||
- ./:/www/
|
||||
- redis-socket:/run/redis-socket
|
||||
environment:
|
||||
- docker=true
|
||||
depends_on:
|
||||
- redis
|
||||
network_mode: host
|
||||
command: php artisan octane:start --server="swoole" --port=7001
|
||||
command: php artisan octane:start --server="swoole" --port=7001 --host=0.0.0.0
|
||||
restart: on-failure
|
||||
horizon:
|
||||
image: ghcr.io/cedar2025/xboard:new
|
||||
volumes:
|
||||
- ./.docker/.data/redis/:/run/redis-socket
|
||||
- ./:/www/
|
||||
- redis-socket:/run/redis-socket
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
command: php artisan horizon
|
||||
@ -23,13 +23,9 @@ services:
|
||||
- redis
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --unixsocket /run/redis-socket/redis.sock --unixsocketperm 777 --save 900 1 --save 300 10 --save 60 10000
|
||||
command: redis-server --unixsocket /data/redis.sock --unixsocketperm 777 --save 900 1 --save 300 10 --save 60 10000
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./.docker/.data/redis:/data
|
||||
- redis-socket:/run/redis-socket
|
||||
sysctls:
|
||||
net.core.somaxconn: 1024
|
||||
volumes:
|
||||
redis-socket: # 定义共享卷用于 socket 通信
|
||||
|
||||
net.core.somaxconn: 1024
|
@ -191,6 +191,9 @@ return new class extends Migration {
|
||||
]);
|
||||
}
|
||||
|
||||
// Update parent_id for all servers
|
||||
$this->updateParentIds();
|
||||
|
||||
// Drop old tables
|
||||
Schema::dropIfExists('v2_server_trojan');
|
||||
Schema::dropIfExists('v2_server_vmess');
|
||||
@ -199,6 +202,56 @@ return new class extends Migration {
|
||||
Schema::dropIfExists('v2_server_hysteria');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update parent_id references for all servers
|
||||
*/
|
||||
private function updateParentIds(): void
|
||||
{
|
||||
// Get all servers that have a parent_id
|
||||
$servers = DB::table('v2_server')
|
||||
->whereNotNull('parent_id')
|
||||
->get();
|
||||
|
||||
// Update each server's parent_id to reference the new table's id
|
||||
foreach ($servers as $server) {
|
||||
$parentId = DB::table('v2_server')
|
||||
->where('type', $server->type)
|
||||
->where('code', $server->parent_id)
|
||||
->value('id');
|
||||
|
||||
if ($parentId) {
|
||||
DB::table('v2_server')
|
||||
->where('id', $server->id)
|
||||
->update(['parent_id' => $parentId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore parent_id references when rolling back
|
||||
*/
|
||||
private function restoreParentIds(string $type, string $table): void
|
||||
{
|
||||
// Get all servers of the specified type that have a parent_id
|
||||
$servers = DB::table($table)
|
||||
->whereNotNull('parent_id')
|
||||
->get();
|
||||
|
||||
// Update each server's parent_id to reference back to the original id
|
||||
foreach ($servers as $server) {
|
||||
$originalParentId = DB::table('v2_server')
|
||||
->where('type', $type)
|
||||
->where('id', $server->parent_id)
|
||||
->value('code');
|
||||
|
||||
if ($originalParentId) {
|
||||
DB::table($table)
|
||||
->where('id', $server->id)
|
||||
->update(['parent_id' => $originalParentId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
@ -457,6 +510,13 @@ return new class extends Migration {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore parent_id references for each server type
|
||||
$this->restoreParentIds('trojan', 'v2_server_trojan');
|
||||
$this->restoreParentIds('vmess', 'v2_server_vmess');
|
||||
$this->restoreParentIds('vless', 'v2_server_vless');
|
||||
$this->restoreParentIds('shadowsocks', 'v2_server_shadowsocks');
|
||||
$this->restoreParentIds('hysteria', 'v2_server_hysteria');
|
||||
|
||||
// Drop new table
|
||||
Schema::dropIfExists('v2_server');
|
||||
}
|
||||
|
2
public/assets/admin/assets/index.css
vendored
2
public/assets/admin/assets/index.css
vendored
File diff suppressed because one or more lines are too long
18
public/assets/admin/assets/index.js
vendored
18
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
348
public/assets/admin/assets/vendor.js
vendored
348
public/assets/admin/assets/vendor.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user