mirror of
https://github.com/lkddi/Xboard.git
synced 2026-04-28 14:57:22 +08:00
feat: new xboard
This commit is contained in:
@@ -10,7 +10,8 @@ class Knowledge extends Model
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'show' => 'boolean',
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
'updated_at' => 'timestamp',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ class Notice extends Model
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'tags' => 'array'
|
||||
'tags' => 'array',
|
||||
'show' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -40,4 +40,22 @@ class Order extends Model
|
||||
self::TYPE_RESET_TRAFFIC => '流量重置',
|
||||
];
|
||||
|
||||
public function payment()
|
||||
{
|
||||
return $this->belongsTo(Payment::class, 'payment_id', 'id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
public function plan()
|
||||
{
|
||||
return $this->belongsTo(Plan::class);
|
||||
}
|
||||
|
||||
public function commission_log()
|
||||
{
|
||||
return $this->hasMany(CommissionLog::class, 'trade_no', 'trade_no');
|
||||
}
|
||||
}
|
||||
|
||||
+376
-5
@@ -3,14 +3,385 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use InvalidArgumentException;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Plan extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'v2_plan';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
|
||||
// 定义流量重置方式
|
||||
public const RESET_TRAFFIC_FOLLOW_SYSTEM = 0; // 跟随系统设置
|
||||
public const RESET_TRAFFIC_FIRST_DAY_MONTH = 1; // 每月1号
|
||||
public const RESET_TRAFFIC_MONTHLY = 2; // 按月重置
|
||||
public const RESET_TRAFFIC_NEVER = 3; // 不重置
|
||||
public const RESET_TRAFFIC_FIRST_DAY_YEAR = 4; // 每年1月1日
|
||||
public const RESET_TRAFFIC_YEARLY = 5; // 按年重置
|
||||
|
||||
// 定义价格类型
|
||||
public const PRICE_TYPE_RESET_TRAFFIC = 'reset_traffic'; // 重置流量价格
|
||||
|
||||
// 定义可用的订阅周期
|
||||
public const PERIOD_MONTHLY = 'monthly';
|
||||
public const PERIOD_QUARTERLY = 'quarterly';
|
||||
public const PERIOD_HALF_YEARLY = 'half_yearly';
|
||||
public const PERIOD_YEARLY = 'yearly';
|
||||
public const PERIOD_TWO_YEARLY = 'two_yearly';
|
||||
public const PERIOD_THREE_YEARLY = 'three_yearly';
|
||||
public const PERIOD_ONETIME = 'onetime';
|
||||
public const PERIOD_RESET_TRAFFIC = 'reset_traffic';
|
||||
|
||||
// 定义旧版周期映射
|
||||
public const LEGACY_PERIOD_MAPPING = [
|
||||
'month_price' => self::PERIOD_MONTHLY,
|
||||
'quarter_price' => self::PERIOD_QUARTERLY,
|
||||
'half_year_price' => self::PERIOD_HALF_YEARLY,
|
||||
'year_price' => self::PERIOD_YEARLY,
|
||||
'two_year_price' => self::PERIOD_TWO_YEARLY,
|
||||
'three_year_price' => self::PERIOD_THREE_YEARLY,
|
||||
'onetime_price' => self::PERIOD_ONETIME,
|
||||
'reset_price' => self::PERIOD_RESET_TRAFFIC
|
||||
];
|
||||
}
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'transfer_enable',
|
||||
'name',
|
||||
'speed_limit',
|
||||
'show',
|
||||
'sort',
|
||||
'renew',
|
||||
'content',
|
||||
'prices',
|
||||
'reset_traffic_method',
|
||||
'capacity_limit',
|
||||
'sell',
|
||||
'device_limit'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'show' => 'boolean',
|
||||
'renew' => 'boolean',
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'integer',
|
||||
'prices' => 'array',
|
||||
'reset_traffic_method' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取所有可用的流量重置方式
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getResetTrafficMethods(): array
|
||||
{
|
||||
return [
|
||||
self::RESET_TRAFFIC_FOLLOW_SYSTEM => '跟随系统设置',
|
||||
self::RESET_TRAFFIC_FIRST_DAY_MONTH => '每月1号',
|
||||
self::RESET_TRAFFIC_MONTHLY => '按月重置',
|
||||
self::RESET_TRAFFIC_NEVER => '不重置',
|
||||
self::RESET_TRAFFIC_FIRST_DAY_YEAR => '每年1月1日',
|
||||
self::RESET_TRAFFIC_YEARLY => '按年重置',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一次流量重置时间
|
||||
*
|
||||
* @param Carbon|null $from 计算起始时间,默认为当前时间
|
||||
* @return Carbon|null 下次重置时间,如果不重置则返回null
|
||||
*/
|
||||
public function getNextResetTime(?Carbon $from = null): ?Carbon
|
||||
{
|
||||
$from = $from ?? Carbon::now();
|
||||
|
||||
switch ($this->reset_traffic_method) {
|
||||
case self::RESET_TRAFFIC_FIRST_DAY_MONTH:
|
||||
return $from->copy()->addMonth()->startOfMonth();
|
||||
|
||||
case self::RESET_TRAFFIC_MONTHLY:
|
||||
return $from->copy()->addMonth()->startOfDay();
|
||||
|
||||
case self::RESET_TRAFFIC_FIRST_DAY_YEAR:
|
||||
return $from->copy()->addYear()->startOfYear();
|
||||
|
||||
case self::RESET_TRAFFIC_YEARLY:
|
||||
return $from->copy()->addYear()->startOfDay();
|
||||
|
||||
case self::RESET_TRAFFIC_NEVER:
|
||||
return null;
|
||||
|
||||
case self::RESET_TRAFFIC_FOLLOW_SYSTEM:
|
||||
default:
|
||||
// 这里需要实现获取系统设置的逻辑
|
||||
// 可以通过系统配置或其他方式获取
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要重置流量
|
||||
*
|
||||
* @param Carbon|null $checkTime 检查时间点,默认为当前时间
|
||||
* @return bool
|
||||
*/
|
||||
public function shouldResetTraffic(?Carbon $checkTime = null): bool
|
||||
{
|
||||
if ($this->reset_traffic_method === self::RESET_TRAFFIC_NEVER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$checkTime = $checkTime ?? Carbon::now();
|
||||
$nextResetTime = $this->getNextResetTime($checkTime);
|
||||
|
||||
if ($nextResetTime === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $checkTime->greaterThanOrEqualTo($nextResetTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量重置方式的描述
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getResetTrafficMethodName(): string
|
||||
{
|
||||
return self::getResetTrafficMethods()[$this->reset_traffic_method] ?? '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的订阅周期
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getAvailablePeriods(): array
|
||||
{
|
||||
return [
|
||||
self::PERIOD_MONTHLY => [
|
||||
'name' => '月付',
|
||||
'days' => 30,
|
||||
'value' => 1
|
||||
],
|
||||
self::PERIOD_QUARTERLY => [
|
||||
'name' => '季付',
|
||||
'days' => 90,
|
||||
'value' => 3
|
||||
],
|
||||
self::PERIOD_HALF_YEARLY => [
|
||||
'name' => '半年付',
|
||||
'days' => 180,
|
||||
'value' => 6
|
||||
],
|
||||
self::PERIOD_YEARLY => [
|
||||
'name' => '年付',
|
||||
'days' => 365,
|
||||
'value' => 12
|
||||
],
|
||||
self::PERIOD_TWO_YEARLY => [
|
||||
'name' => '两年付',
|
||||
'days' => 730,
|
||||
'value' => 24
|
||||
],
|
||||
self::PERIOD_THREE_YEARLY => [
|
||||
'name' => '三年付',
|
||||
'days' => 1095,
|
||||
'value' => 36
|
||||
],
|
||||
self::PERIOD_ONETIME => [
|
||||
'name' => '一次性',
|
||||
'days' => -1,
|
||||
'value' => -1
|
||||
],
|
||||
self::PERIOD_RESET_TRAFFIC => [
|
||||
'name' => '重置流量',
|
||||
'days' => -1,
|
||||
'value' => -1
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定周期的价格
|
||||
*
|
||||
* @param string $period
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPriceByPeriod(string $period): ?int
|
||||
{
|
||||
return $this->prices[$period] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已设置价格的周期
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getActivePeriods(): array
|
||||
{
|
||||
return array_filter(
|
||||
self::getAvailablePeriods(),
|
||||
fn($period) => isset($this->prices[$period])
|
||||
&& $this->prices[$period] > 0,
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置指定周期的价格
|
||||
*
|
||||
* @param string $period
|
||||
* @param int $price
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setPeriodPrice(string $period, int $price): void
|
||||
{
|
||||
if (!array_key_exists($period, self::getAvailablePeriods())) {
|
||||
throw new InvalidArgumentException("Invalid period: {$period}");
|
||||
}
|
||||
|
||||
$prices = $this->prices ?? [];
|
||||
$prices[$period] = $price;
|
||||
$this->prices = $prices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除指定周期的价格
|
||||
*
|
||||
* @param string $period
|
||||
* @return void
|
||||
*/
|
||||
public function removePeriodPrice(string $period): void
|
||||
{
|
||||
$prices = $this->prices ?? [];
|
||||
unset($prices[$period]);
|
||||
$this->prices = $prices;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有价格及其对应的周期信息
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPriceList(): array
|
||||
{
|
||||
$prices = $this->prices ?? [];
|
||||
$periods = self::getAvailablePeriods();
|
||||
|
||||
$priceList = [];
|
||||
foreach ($prices as $period => $price) {
|
||||
if (isset($periods[$period]) && $price > 0) {
|
||||
$priceList[$period] = [
|
||||
'period' => $periods[$period],
|
||||
'price' => $price,
|
||||
'average_price' => $periods[$period]['value'] > 0
|
||||
? round($price / $periods[$period]['value'], 2)
|
||||
: $price
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $priceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以重置流量
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canResetTraffic(): bool
|
||||
{
|
||||
return $this->reset_traffic_method !== self::RESET_TRAFFIC_NEVER
|
||||
&& $this->getResetTrafficPrice() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取重置流量的价格
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getResetTrafficPrice(): int
|
||||
{
|
||||
return $this->prices[self::PRICE_TYPE_RESET_TRAFFIC] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定周期的有效天数
|
||||
*
|
||||
* @param string $period
|
||||
* @return int -1表示永久有效
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function getPeriodDays(string $period): int
|
||||
{
|
||||
$periods = self::getAvailablePeriods();
|
||||
if (!isset($periods[$period])) {
|
||||
throw new InvalidArgumentException("Invalid period: {$period}");
|
||||
}
|
||||
|
||||
return $periods[$period]['days'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查周期是否有效
|
||||
*
|
||||
* @param string $period
|
||||
* @return bool
|
||||
*/
|
||||
public static function isValidPeriod(string $period): bool
|
||||
{
|
||||
return array_key_exists($period, self::getAvailablePeriods());
|
||||
}
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
return $this->hasOne(ServerGroup::class, 'id', 'group_id');
|
||||
}
|
||||
|
||||
public function orders(): HasMany
|
||||
{
|
||||
return $this->hasMany(Order::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置流量重置方式
|
||||
*
|
||||
* @param int $method
|
||||
* @return void
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setResetTrafficMethod(int $method): void
|
||||
{
|
||||
if (!array_key_exists($method, self::getResetTrafficMethods())) {
|
||||
throw new InvalidArgumentException("Invalid reset traffic method: {$method}");
|
||||
}
|
||||
|
||||
$this->reset_traffic_method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置重置流量价格
|
||||
*
|
||||
* @param int $price
|
||||
* @return void
|
||||
*/
|
||||
public function setResetTrafficPrice(int $price): void
|
||||
{
|
||||
$prices = $this->prices ?? [];
|
||||
$prices[self::PRICE_TYPE_RESET_TRAFFIC] = max(0, $price);
|
||||
$this->prices = $prices;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Plugin extends Model
|
||||
{
|
||||
protected $table = 'v2_plugins';
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
'created_at',
|
||||
'updated_at'
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Helper;
|
||||
use App\Models\User;
|
||||
|
||||
class Server extends Model
|
||||
{
|
||||
public const TYPE_HYSTERIA = 'hysteria';
|
||||
public const TYPE_VLESS = 'vless';
|
||||
public const TYPE_TROJAN = 'trojan';
|
||||
public const TYPE_VMESS = 'vmess';
|
||||
public const TYPE_TUIC = 'tuic';
|
||||
public const TYPE_SHADOWSOCKS = 'shadowsocks';
|
||||
|
||||
public const STATUS_OFFLINE = 0;
|
||||
public const STATUS_ONLINE_NO_PUSH = 1;
|
||||
public const STATUS_ONLINE = 2;
|
||||
|
||||
public const CHECK_INTERVAL = 300; // 5 minutes in seconds
|
||||
|
||||
private const CIPHER_CONFIGURATIONS = [
|
||||
'2022-blake3-aes-128-gcm' => [
|
||||
'serverKeySize' => 16,
|
||||
'userKeySize' => 16,
|
||||
],
|
||||
'2022-blake3-aes-256-gcm' => [
|
||||
'serverKeySize' => 32,
|
||||
'userKeySize' => 32,
|
||||
],
|
||||
'2022-blake3-chacha20-poly1305' => [
|
||||
'serverKeySize' => 32,
|
||||
'userKeySize' => 32,
|
||||
]
|
||||
];
|
||||
|
||||
public const TYPE_ALIASES = [
|
||||
'v2ray' => self::TYPE_VMESS,
|
||||
'hysteria2' => self::TYPE_HYSTERIA,
|
||||
];
|
||||
|
||||
public const VALID_TYPES = [
|
||||
self::TYPE_HYSTERIA,
|
||||
self::TYPE_VLESS,
|
||||
self::TYPE_TROJAN,
|
||||
self::TYPE_VMESS,
|
||||
self::TYPE_TUIC,
|
||||
self::TYPE_SHADOWSOCKS,
|
||||
];
|
||||
|
||||
protected $table = 'v2_server';
|
||||
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'group_ids' => 'array',
|
||||
'route_ids' => 'array',
|
||||
'tags' => 'array',
|
||||
'protocol_settings' => 'array',
|
||||
'last_check_at' => 'integer',
|
||||
'last_push_at' => 'integer',
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
];
|
||||
|
||||
private const PROTOCOL_CONFIGURATIONS = [
|
||||
self::TYPE_TROJAN => [
|
||||
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||
'server_name' => ['type' => 'string', 'default' => null],
|
||||
'network' => ['type' => 'string', 'default' => null],
|
||||
'network_settings' => ['type' => 'array', 'default' => null]
|
||||
],
|
||||
self::TYPE_VMESS => [
|
||||
'tls' => ['type' => 'integer', 'default' => 0],
|
||||
'network' => ['type' => 'string', 'default' => null],
|
||||
'rules' => ['type' => 'array', 'default' => null],
|
||||
'network_settings' => ['type' => 'array', 'default' => null],
|
||||
'tls_settings' => ['type' => 'array', 'default' => null]
|
||||
],
|
||||
self::TYPE_VLESS => [
|
||||
'tls' => ['type' => 'integer', 'default' => 0],
|
||||
'tls_settings' => ['type' => 'array', 'default' => null],
|
||||
'flow' => ['type' => 'string', 'default' => null],
|
||||
'network' => ['type' => 'string', 'default' => null],
|
||||
'network_settings' => ['type' => 'array', 'default' => null],
|
||||
'reality_settings' => [
|
||||
'type' => 'object',
|
||||
'fields' => [
|
||||
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||
'server_port' => ['type' => 'string', 'default' => null],
|
||||
'server_name' => ['type' => 'string', 'default' => null],
|
||||
'public_key' => ['type' => 'string', 'default' => null],
|
||||
'private_key' => ['type' => 'string', 'default' => null],
|
||||
'short_id' => ['type' => 'string', 'default' => null]
|
||||
]
|
||||
]
|
||||
],
|
||||
self::TYPE_SHADOWSOCKS => [
|
||||
'cipher' => ['type' => 'string', 'default' => null],
|
||||
'obfs' => ['type' => 'string', 'default' => null],
|
||||
'obfs_settings' => ['type' => 'array', 'default' => null]
|
||||
],
|
||||
self::TYPE_HYSTERIA => [
|
||||
'version' => ['type' => 'integer', 'default' => 2],
|
||||
'bandwidth' => [
|
||||
'type' => 'object',
|
||||
'fields' => [
|
||||
'up' => ['type' => 'integer', 'default' => null],
|
||||
'down' => ['type' => 'integer', 'default' => null]
|
||||
]
|
||||
],
|
||||
'obfs' => [
|
||||
'type' => 'object',
|
||||
'fields' => [
|
||||
'open' => ['type' => 'boolean', 'default' => false],
|
||||
'type' => ['type' => 'string', 'default' => 'salamander'],
|
||||
'password' => ['type' => 'string', 'default' => null]
|
||||
]
|
||||
],
|
||||
'tls' => [
|
||||
'type' => 'object',
|
||||
'fields' => [
|
||||
'server_name' => ['type' => 'string', 'default' => null],
|
||||
'allow_insecure' => ['type' => 'boolean', 'default' => false]
|
||||
]
|
||||
]
|
||||
],
|
||||
self::TYPE_TUIC => [
|
||||
'congestion_control' => ['type' => 'string', 'default' => 'cubic'],
|
||||
'alpn' => ['type' => 'array', 'default' => ['h3']],
|
||||
'udp_relay_mode' => ['type' => 'string', 'default' => 'native'],
|
||||
'allow_insecure' => ['type' => 'boolean', 'default' => false],
|
||||
'tls_settings' => ['type' => 'array', 'default' => null]
|
||||
]
|
||||
];
|
||||
|
||||
private function castValueWithConfig($value, array $config)
|
||||
{
|
||||
if ($value === null && $config['type'] !== 'object') {
|
||||
return $config['default'] ?? null;
|
||||
}
|
||||
|
||||
return match ($config['type']) {
|
||||
'integer' => (int) $value,
|
||||
'boolean' => (bool) $value,
|
||||
'string' => (string) $value,
|
||||
'array' => (array) $value,
|
||||
'object' => is_array($value) ?
|
||||
$this->castSettingsWithConfig($value, $config['fields']) :
|
||||
$config['default'] ?? null,
|
||||
default => $value
|
||||
};
|
||||
}
|
||||
|
||||
private function castSettingsWithConfig(array $settings, array $configs): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($configs as $key => $config) {
|
||||
$value = $settings[$key] ?? null;
|
||||
$result[$key] = $this->castValueWithConfig($value, $config);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getDefaultSettings(array $configs): array
|
||||
{
|
||||
$defaults = [];
|
||||
foreach ($configs as $key => $config) {
|
||||
if ($config['type'] === 'object') {
|
||||
$defaults[$key] = $this->getDefaultSettings($config['fields']);
|
||||
} else {
|
||||
$defaults[$key] = $config['default'];
|
||||
}
|
||||
}
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
public function getProtocolSettingsAttribute($value)
|
||||
{
|
||||
$settings = json_decode($value, true) ?? [];
|
||||
$configs = self::PROTOCOL_CONFIGURATIONS[$this->type] ?? [];
|
||||
return $this->castSettingsWithConfig($settings, $configs);
|
||||
}
|
||||
|
||||
public function setProtocolSettingsAttribute($value)
|
||||
{
|
||||
if (is_string($value)) {
|
||||
$value = json_decode($value, true);
|
||||
}
|
||||
|
||||
$configs = self::PROTOCOL_CONFIGURATIONS[$this->type] ?? [];
|
||||
$castedSettings = $this->castSettingsWithConfig($value ?? [], $configs);
|
||||
|
||||
$this->attributes['protocol_settings'] = json_encode($castedSettings);
|
||||
}
|
||||
|
||||
public function loadParentCreatedAt(): void
|
||||
{
|
||||
if ($this->parent_id) {
|
||||
$this->created_at = $this->parent()->value('created_at');
|
||||
}
|
||||
}
|
||||
|
||||
public function loadServerStatus(): void
|
||||
{
|
||||
$type = strtoupper($this->type);
|
||||
$serverId = $this->parent_id ?: $this->id;
|
||||
|
||||
$this->last_check_at = Cache::get(CacheKey::get("SERVER_{$type}_LAST_CHECK_AT", $serverId));
|
||||
$this->last_push_at = Cache::get(CacheKey::get("SERVER_{$type}_LAST_PUSH_AT", $serverId));
|
||||
$this->online = Cache::get(CacheKey::get("SERVER_{$type}_ONLINE_USER", $serverId)) ?? 0;
|
||||
$this->is_online = (time() - 300 > $this->last_check_at) ? 0 : 1;
|
||||
$this->available_status = $this->getAvailableStatus();
|
||||
$this->cache_key = "{$this->type}-{$this->id}-{$this->updated_at}-{$this->is_online}";
|
||||
}
|
||||
|
||||
public function handlePortAllocation(): void
|
||||
{
|
||||
if (strpos($this->port, '-') !== false) {
|
||||
$this->ports = $this->port;
|
||||
$this->port = Helper::randomPort($this->port);
|
||||
} else {
|
||||
$this->port = (int) $this->port;
|
||||
}
|
||||
}
|
||||
|
||||
public function generateShadowsocksPassword(User $user): void
|
||||
{
|
||||
if ($this->type !== self::TYPE_SHADOWSOCKS) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->password = $user->uuid;
|
||||
|
||||
$cipher = data_get($this, 'protocol_settings.cipher');
|
||||
if (!$cipher || !isset(self::CIPHER_CONFIGURATIONS[$cipher])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$config = self::CIPHER_CONFIGURATIONS[$cipher];
|
||||
$serverKey = Helper::getServerKey($this->created_at, $config['serverKeySize']);
|
||||
$userKey = Helper::uuidToBase64($user->uuid, $config['userKeySize']);
|
||||
$this->password = "{$serverKey}:{$userKey}";
|
||||
}
|
||||
|
||||
public static function normalizeType(string $type): string
|
||||
{
|
||||
return strtolower(self::TYPE_ALIASES[$type] ?? $type);
|
||||
}
|
||||
|
||||
public static function isValidType(string $type): bool
|
||||
{
|
||||
return in_array(self::normalizeType($type), self::VALID_TYPES, true);
|
||||
}
|
||||
|
||||
public function getAvailableStatus(): int
|
||||
{
|
||||
$now = time();
|
||||
if (!$this->last_check_at || ($now - self::CHECK_INTERVAL) >= $this->last_check_at) {
|
||||
return self::STATUS_OFFLINE;
|
||||
}
|
||||
if (!$this->last_push_at || ($now - self::CHECK_INTERVAL) >= $this->last_push_at) {
|
||||
return self::STATUS_ONLINE_NO_PUSH;
|
||||
}
|
||||
return self::STATUS_ONLINE;
|
||||
}
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
|
||||
public function stats(): HasMany
|
||||
{
|
||||
return $this->hasMany(StatServer::class, 'server_id', 'id');
|
||||
}
|
||||
|
||||
public function groups()
|
||||
{
|
||||
return ServerGroup::whereIn('id', $this->group_ids)->get();
|
||||
}
|
||||
|
||||
public function routes()
|
||||
{
|
||||
return ServerRoute::whereIn('id', $this->route_ids)->get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class ServerGroup extends Model
|
||||
{
|
||||
@@ -12,4 +13,14 @@ class ServerGroup extends Model
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
];
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class, 'group_id', 'id');
|
||||
}
|
||||
|
||||
public function servers()
|
||||
{
|
||||
return Server::whereJsonContains('group_ids', (string) $this->id)->get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
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',
|
||||
'ips' => 'array',
|
||||
'excludes' => 'array'
|
||||
];
|
||||
|
||||
// ALPN映射表
|
||||
public static $alpnMap = [
|
||||
0 => 'hysteria',
|
||||
1 => 'http/1.1',
|
||||
2 => 'h2',
|
||||
3 => 'h3'
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ServerTrojan extends Model
|
||||
{
|
||||
protected $table = 'v2_server_trojan';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'array',
|
||||
'route_id' => 'array',
|
||||
'networkSettings' => 'array',
|
||||
'tags' => 'array',
|
||||
'excludes' => 'array',
|
||||
'ips' => 'array'
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ServerVless extends Model
|
||||
{
|
||||
protected $table = 'v2_server_vless';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'array',
|
||||
'route_id' => 'array',
|
||||
'tls_settings' => 'array',
|
||||
'network_settings' => 'array',
|
||||
'tags' => 'array',
|
||||
'excludes' => 'array',
|
||||
'ips' => 'array'
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ServerVmess extends Model
|
||||
{
|
||||
protected $table = 'v2_server_vmess';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'array',
|
||||
'route_id' => 'array',
|
||||
'tlsSettings' => 'array',
|
||||
'networkSettings' => 'array',
|
||||
'dnsSettings' => 'array',
|
||||
'ruleSettings' => 'array',
|
||||
'tags' => 'array',
|
||||
'excludes' => 'array',
|
||||
'ips' => 'array'
|
||||
];
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,9 @@ class StatServer extends Model
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
];
|
||||
|
||||
public function server()
|
||||
{
|
||||
return $this->belongsTo(Server::class, 'server_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,15 @@ class Ticket extends Model
|
||||
self::STATUS_CLOSED => '关闭'
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id', 'id');
|
||||
}
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(TicketMessage::class, 'ticket_id', 'id');
|
||||
}
|
||||
// 即将删除
|
||||
public function message()
|
||||
{
|
||||
return $this->hasMany(TicketMessage::class, 'ticket_id', 'id');
|
||||
|
||||
+14
-1
@@ -3,10 +3,12 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Model
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens;
|
||||
protected $table = 'v2_user';
|
||||
protected $dateFormat = 'U';
|
||||
protected $guarded = ['id'];
|
||||
@@ -14,6 +16,7 @@ class User extends Model
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
];
|
||||
protected $hidden = ['password'];
|
||||
|
||||
|
||||
// 获取邀请人信息
|
||||
@@ -28,6 +31,11 @@ class User extends Model
|
||||
return $this->belongsTo(Plan::class, 'plan_id', 'id');
|
||||
}
|
||||
|
||||
public function group()
|
||||
{
|
||||
return $this->belongsTo(ServerGroup::class, 'group_id', 'id');
|
||||
}
|
||||
|
||||
// 获取用户邀请码列表
|
||||
public function codes()
|
||||
{
|
||||
@@ -39,4 +47,9 @@ class User extends Model
|
||||
{
|
||||
return $this->hasMany(Ticket::class, 'user_id', 'id');
|
||||
}
|
||||
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id', 'id');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user