$stats 节点统计 * * @property-read int|null $last_check_at 最后检查时间(Unix时间戳) * @property-read int|null $last_push_at 最后推送时间(Unix时间戳) * @property-read int $online 在线用户数 * @property-read int $online_conn 在线连接数 * @property-read array|null $metrics 节点指标指标 * @property-read int $is_online 是否在线(1在线 0离线) * @property-read string $available_status 可用状态描述 * @property-read string $cache_key 缓存键 * @property string|null $ports 端口范围 * @property string|null $password 密码 * @property int|null $u 上行流量 * @property int|null $d 下行流量 * @property int|null $total 总流量 * @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息) * * @property int $transfer_enable 流量上限,0或者null表示不限制 * @property int $u 当前上传流量 * @property int $d 当前下载流量 */ 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 TYPE_ANYTLS = 'anytls'; public const TYPE_SOCKS = 'socks'; public const TYPE_NAIVE = 'naive'; public const TYPE_HTTP = 'http'; public const TYPE_MIERU = 'mieru'; 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, self::TYPE_ANYTLS, self::TYPE_SOCKS, self::TYPE_NAIVE, self::TYPE_HTTP, self::TYPE_MIERU, ]; protected $table = 'v2_server'; protected $guarded = ['id']; protected $casts = [ 'group_ids' => 'array', 'route_ids' => 'array', 'tags' => 'array', 'protocol_settings' => 'array', 'custom_outbounds' => 'array', 'custom_routes' => 'array', 'cert_config' => 'array', 'last_check_at' => 'integer', 'last_push_at' => 'integer', 'show' => 'boolean', 'created_at' => 'timestamp', 'updated_at' => 'timestamp', 'rate_time_ranges' => 'array', 'rate_time_enable' => 'boolean', 'transfer_enable' => 'integer', 'u' => 'integer', 'd' => 'integer', ]; private const MULTIPLEX_CONFIGURATION = [ 'multiplex' => [ 'type' => 'object', 'fields' => [ 'enabled' => ['type' => 'boolean', 'default' => false], 'protocol' => ['type' => 'string', 'default' => 'yamux'], 'max_connections' => ['type' => 'integer', 'default' => null], // 'min_streams' => ['type' => 'integer', 'default' => null], // 'max_streams' => ['type' => 'integer', 'default' => null], 'padding' => ['type' => 'boolean', 'default' => false], 'brutal' => [ 'type' => 'object', 'fields' => [ 'enabled' => ['type' => 'boolean', 'default' => false], 'up_mbps' => ['type' => 'integer', 'default' => null], 'down_mbps' => ['type' => 'integer', 'default' => null], ] ] ] ] ]; private const REALITY_CONFIGURATION = [ 'reality_settings' => [ 'type' => 'object', 'fields' => [ 'server_name' => ['type' => 'string', 'default' => null], 'server_port' => ['type' => 'string', 'default' => null], 'public_key' => ['type' => 'string', 'default' => null], 'private_key' => ['type' => 'string', 'default' => null], 'short_id' => ['type' => 'string', 'default' => null], 'allow_insecure' => ['type' => 'boolean', 'default' => false], ] ] ]; private const UTLS_CONFIGURATION = [ 'utls' => [ 'type' => 'object', 'fields' => [ 'enabled' => ['type' => 'boolean', 'default' => false], 'fingerprint' => ['type' => 'string', 'default' => 'chrome'], ] ] ]; private const PROTOCOL_CONFIGURATIONS = [ self::TYPE_TROJAN => [ 'tls' => ['type' => 'integer', 'default' => 1], 'network' => ['type' => 'string', 'default' => null], 'network_settings' => ['type' => 'array', 'default' => null], 'server_name' => ['type' => 'string', 'default' => null], 'allow_insecure' => ['type' => 'boolean', 'default' => false], ...self::REALITY_CONFIGURATION, ...self::MULTIPLEX_CONFIGURATION, ...self::UTLS_CONFIGURATION ], 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::MULTIPLEX_CONFIGURATION, ...self::UTLS_CONFIGURATION ], self::TYPE_VLESS => [ 'tls' => ['type' => 'integer', 'default' => 0], 'tls_settings' => ['type' => 'array', 'default' => null], 'flow' => ['type' => 'string', 'default' => null], 'encryption' => [ 'type' => 'object', 'default' => null, 'fields' => [ 'enabled' => ['type' => 'boolean', 'default' => false], 'encryption' => ['type' => 'string', 'default' => null], // 客户端公钥 'decryption' => ['type' => 'string', 'default' => null], // 服务端私钥 ] ], 'network' => ['type' => 'string', 'default' => null], 'network_settings' => ['type' => 'array', 'default' => null], ...self::REALITY_CONFIGURATION, ...self::MULTIPLEX_CONFIGURATION, ...self::UTLS_CONFIGURATION ], self::TYPE_SHADOWSOCKS => [ 'cipher' => ['type' => 'string', 'default' => null], 'obfs' => ['type' => 'string', 'default' => null], 'obfs_settings' => ['type' => 'array', 'default' => null], 'plugin' => ['type' => 'string', 'default' => null], 'plugin_opts' => ['type' => 'string', '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] ] ], 'hop_interval' => ['type' => 'integer', 'default' => null] ], self::TYPE_TUIC => [ 'version' => ['type' => 'integer', 'default' => 5], 'congestion_control' => ['type' => 'string', 'default' => 'cubic'], 'alpn' => ['type' => 'array', 'default' => ['h3']], 'udp_relay_mode' => ['type' => 'string', 'default' => 'native'], 'tls' => [ 'type' => 'object', 'fields' => [ 'server_name' => ['type' => 'string', 'default' => null], 'allow_insecure' => ['type' => 'boolean', 'default' => false] ] ] ], self::TYPE_ANYTLS => [ 'padding_scheme' => [ 'type' => 'array', 'default' => [ "stop=8", "0=30-30", "1=100-400", "2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000", "3=9-9,500-1000", "4=500-1000", "5=500-1000", "6=500-1000", "7=500-1000" ] ], 'tls' => [ 'type' => 'object', 'fields' => [ 'server_name' => ['type' => 'string', 'default' => null], 'allow_insecure' => ['type' => 'boolean', 'default' => false] ] ] ], self::TYPE_SOCKS => [ 'tls' => ['type' => 'integer', 'default' => 0], 'tls_settings' => [ 'type' => 'object', 'fields' => [ 'allow_insecure' => ['type' => 'boolean', 'default' => false] ] ] ], self::TYPE_NAIVE => [ 'tls' => ['type' => 'integer', 'default' => 0], 'tls_settings' => ['type' => 'array', 'default' => null] ], self::TYPE_HTTP => [ 'tls' => ['type' => 'integer', 'default' => 0], 'tls_settings' => [ 'type' => 'object', 'fields' => [ 'allow_insecure' => ['type' => 'boolean', 'default' => false], 'server_name' => ['type' => 'string', 'default' => null] ] ] ], self::TYPE_MIERU => [ 'transport' => ['type' => 'string', 'default' => 'TCP'], 'traffic_pattern' => ['type' => 'string', 'default' => ''], ...self::MULTIPLEX_CONFIGURATION, ] ]; 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; } 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 generateServerPassword(User $user): string { if ($this->type !== self::TYPE_SHADOWSOCKS) { return $user->uuid; } $cipher = data_get($this, 'protocol_settings.cipher'); if (!$cipher || !isset(self::CIPHER_CONFIGURATIONS[$cipher])) { return $user->uuid; } $config = self::CIPHER_CONFIGURATIONS[$cipher]; // Use parent's created_at if this is a child node $serverCreatedAt = $this->parent_id ? $this->parent->created_at : $this->created_at; $serverKey = Helper::getServerKey($serverCreatedAt, $config['serverKeySize']); $userKey = Helper::uuidToBase64($user->uuid, $config['userKeySize']); return "{$serverKey}:{$userKey}"; } public static function normalizeType(?string $type): string | null { return $type ? strtolower(self::TYPE_ALIASES[$type] ?? $type) : null; } public static function isValidType(?string $type): bool { return $type ? in_array(self::normalizeType($type), self::VALID_TYPES, true) : true; } public function getAvailableStatusAttribute(): 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(); } /** * 最后检查时间访问器 */ protected function lastCheckAt(): Attribute { return Attribute::make( get: function () { $type = strtoupper($this->type); $serverId = $this->parent_id ?: $this->id; return Cache::get(CacheKey::get("SERVER_{$type}_LAST_CHECK_AT", $serverId)); } ); } /** * 最后推送时间访问器 */ protected function lastPushAt(): Attribute { return Attribute::make( get: function () { $type = strtoupper($this->type); $serverId = $this->parent_id ?: $this->id; return Cache::get(CacheKey::get("SERVER_{$type}_LAST_PUSH_AT", $serverId)); } ); } /** * 在线用户数访问器 */ protected function online(): Attribute { return Attribute::make( get: function () { $type = strtoupper($this->type); $serverId = $this->parent_id ?: $this->id; return Cache::get(CacheKey::get("SERVER_{$type}_ONLINE_USER", $serverId)) ?? 0; } ); } /** * 是否在线访问器 */ protected function isOnline(): Attribute { return Attribute::make( get: function () { return (time() - 300 > $this->last_check_at) ? 0 : 1; } ); } /** * 缓存键访问器 */ protected function cacheKey(): Attribute { return Attribute::make( get: function () { return "{$this->type}-{$this->id}-{$this->updated_at}-{$this->is_online}"; } ); } /** * 服务器密钥访问器 */ protected function serverKey(): Attribute { return Attribute::make( get: function () { if ($this->type === self::TYPE_SHADOWSOCKS) { return Helper::getServerKey($this->created_at, 16); } return null; } ); } /** * 指标指标访问器 */ protected function metrics(): Attribute { return Attribute::make( get: function () { $type = strtoupper($this->type); $serverId = $this->parent_id ?: $this->id; return Cache::get(CacheKey::get("SERVER_{$type}_METRICS", $serverId)); } ); } /** * 在线连接数访问器 */ protected function onlineConn(): Attribute { return Attribute::make( get: function () { return $this->metrics['active_connections'] ?? 0; } ); } /** * 负载状态访问器 */ protected function loadStatus(): Attribute { return Attribute::make( get: function () { $type = strtoupper($this->type); $serverId = $this->parent_id ?: $this->id; return Cache::get(CacheKey::get("SERVER_{$type}_LOAD_STATUS", $serverId)); } ); } public function getCurrentRate(): float { if (!$this->rate_time_enable) { return (float) $this->rate; } $now = now()->format('H:i'); $ranges = $this->rate_time_ranges ?? []; $matchedRange = collect($ranges) ->first(fn($range) => $now >= $range['start'] && $now <= $range['end']); return $matchedRange ? (float) $matchedRange['rate'] : (float) $this->rate; } }