first commit
This commit is contained in:
262
Xboard/app/Support/AbstractProtocol.php
Normal file
262
Xboard/app/Support/AbstractProtocol.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Services\Plugin\HookManager;
|
||||
|
||||
abstract class AbstractProtocol
|
||||
{
|
||||
/**
|
||||
* @var array 用户信息
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var array 服务器信息
|
||||
*/
|
||||
protected $servers;
|
||||
|
||||
/**
|
||||
* @var string|null 客户端名称
|
||||
*/
|
||||
protected $clientName;
|
||||
|
||||
/**
|
||||
* @var string|null 客户端版本
|
||||
*/
|
||||
protected $clientVersion;
|
||||
|
||||
/**
|
||||
* @var string|null 原始 User-Agent
|
||||
*/
|
||||
protected $userAgent;
|
||||
|
||||
/**
|
||||
* @var array 协议标识
|
||||
*/
|
||||
public $flags = [];
|
||||
|
||||
/**
|
||||
* @var array 协议需求配置
|
||||
*/
|
||||
protected $protocolRequirements = [];
|
||||
|
||||
/**
|
||||
* @var array 允许的协议类型(白名单) 为空则不进行过滤
|
||||
*/
|
||||
protected $allowedProtocols = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param array $user 用户信息
|
||||
* @param array $servers 服务器信息
|
||||
* @param string|null $clientName 客户端名称
|
||||
* @param string|null $clientVersion 客户端版本
|
||||
* @param string|null $userAgent 原始 User-Agent
|
||||
*/
|
||||
public function __construct($user, $servers, $clientName = null, $clientVersion = null, $userAgent = null)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->servers = $servers;
|
||||
$this->clientName = $clientName;
|
||||
$this->clientVersion = $clientVersion;
|
||||
$this->userAgent = $userAgent;
|
||||
$this->protocolRequirements = $this->normalizeProtocolRequirements($this->protocolRequirements);
|
||||
$this->servers = HookManager::filter('protocol.servers.filtered', $this->filterServersByVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取协议标识
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getFlags(): array
|
||||
{
|
||||
return $this->flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function handle();
|
||||
|
||||
/**
|
||||
* 根据客户端版本过滤不兼容的服务器
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function filterServersByVersion()
|
||||
{
|
||||
$this->filterByAllowedProtocols();
|
||||
$hasGlobalConfig = isset($this->protocolRequirements['*']);
|
||||
$hasClientConfig = isset($this->protocolRequirements[$this->clientName]);
|
||||
|
||||
if ((blank($this->clientName) || blank($this->clientVersion)) && !$hasGlobalConfig) {
|
||||
return $this->servers;
|
||||
}
|
||||
|
||||
if (!$hasGlobalConfig && !$hasClientConfig) {
|
||||
return $this->servers;
|
||||
}
|
||||
|
||||
return collect($this->servers)
|
||||
->filter(fn($server) => $this->isCompatible($server))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查服务器是否与当前客户端兼容
|
||||
*
|
||||
* @param array $server 服务器信息
|
||||
* @return bool
|
||||
*/
|
||||
protected function isCompatible($server)
|
||||
{
|
||||
$serverType = $server['type'] ?? null;
|
||||
if (isset($this->protocolRequirements['*'][$serverType])) {
|
||||
$globalRequirements = $this->protocolRequirements['*'][$serverType];
|
||||
if (!$this->checkRequirements($globalRequirements, $server)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->protocolRequirements[$this->clientName][$serverType])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$requirements = $this->protocolRequirements[$this->clientName][$serverType];
|
||||
return $this->checkRequirements($requirements, $server);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查版本要求
|
||||
*
|
||||
* @param array $requirements 要求配置
|
||||
* @param array $server 服务器信息
|
||||
* @return bool
|
||||
*/
|
||||
private function checkRequirements(array $requirements, array $server): bool
|
||||
{
|
||||
foreach ($requirements as $field => $filterRule) {
|
||||
if (in_array($field, ['base_version', 'incompatible'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$actualValue = data_get($server, $field);
|
||||
|
||||
if (is_array($filterRule) && isset($filterRule['whitelist'])) {
|
||||
$allowedValues = $filterRule['whitelist'];
|
||||
$strict = $filterRule['strict'] ?? false;
|
||||
if ($strict) {
|
||||
if ($actualValue === null) {
|
||||
return false;
|
||||
}
|
||||
if (!is_string($actualValue) && !is_int($actualValue)) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($allowedValues[$actualValue])) {
|
||||
return false;
|
||||
}
|
||||
$requiredVersion = $allowedValues[$actualValue];
|
||||
if ($requiredVersion !== '0.0.0' && version_compare($this->clientVersion, $requiredVersion, '<')) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$allowedValues = $filterRule;
|
||||
$strict = false;
|
||||
}
|
||||
|
||||
if ($actualValue === null) {
|
||||
continue;
|
||||
}
|
||||
if (!is_string($actualValue) && !is_int($actualValue)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($allowedValues[$actualValue])) {
|
||||
continue;
|
||||
}
|
||||
$requiredVersion = $allowedValues[$actualValue];
|
||||
if ($requiredVersion !== '0.0.0' && version_compare($this->clientVersion, $requiredVersion, '<')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前客户端是否支持特定功能
|
||||
*
|
||||
* @param string $clientName 客户端名称
|
||||
* @param string $minVersion 最低版本要求
|
||||
* @param array $additionalConditions 额外条件检查
|
||||
* @return bool
|
||||
*/
|
||||
protected function supportsFeature(string $clientName, string $minVersion, array $additionalConditions = []): bool
|
||||
{
|
||||
// 检查客户端名称
|
||||
if ($this->clientName !== $clientName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查版本号
|
||||
if (empty($this->clientVersion) || version_compare($this->clientVersion, $minVersion, '<')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查额外条件
|
||||
foreach ($additionalConditions as $condition) {
|
||||
if (!$condition) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据白名单过滤服务器
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function filterByAllowedProtocols(): void
|
||||
{
|
||||
if (!empty($this->allowedProtocols)) {
|
||||
$this->servers = collect($this->servers)
|
||||
->filter(fn($server) => in_array($server['type'], $this->allowedProtocols))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将平铺的协议需求转换为树形结构
|
||||
*
|
||||
* @param array $flat 平铺的协议需求
|
||||
* @return array 树形结构的协议需求
|
||||
*/
|
||||
protected function normalizeProtocolRequirements(array $flat): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($flat as $key => $value) {
|
||||
if (!str_contains($key, '.')) {
|
||||
$result[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
$segments = explode('.', $key, 3);
|
||||
if (count($segments) < 3) {
|
||||
$result[$segments[0]][$segments[1] ?? '*'][''] = $value;
|
||||
continue;
|
||||
}
|
||||
[$client, $type, $field] = $segments;
|
||||
$result[$client][$type][$field] = $value;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
162
Xboard/app/Support/ProtocolManager.php
Normal file
162
Xboard/app/Support/ProtocolManager.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Illuminate\Contracts\Container\Container;
|
||||
|
||||
class ProtocolManager
|
||||
{
|
||||
/**
|
||||
* @var Container Laravel容器实例
|
||||
*/
|
||||
protected $container;
|
||||
|
||||
/**
|
||||
* @var array 缓存的协议类列表
|
||||
*/
|
||||
protected $protocolClasses = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param Container $container
|
||||
*/
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发现并注册所有协议类
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function registerAllProtocols()
|
||||
{
|
||||
if (empty($this->protocolClasses)) {
|
||||
$files = glob(app_path('Protocols') . '/*.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$className = 'App\\Protocols\\' . basename($file, '.php');
|
||||
|
||||
if (class_exists($className) && is_subclass_of($className, AbstractProtocol::class)) {
|
||||
$this->protocolClasses[] = $className;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的协议类
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getProtocolClasses()
|
||||
{
|
||||
if (empty($this->protocolClasses)) {
|
||||
$this->registerAllProtocols();
|
||||
}
|
||||
|
||||
return $this->protocolClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有协议的标识
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAllFlags()
|
||||
{
|
||||
return collect($this->getProtocolClasses())
|
||||
->map(function ($class) {
|
||||
try {
|
||||
$reflection = new \ReflectionClass($class);
|
||||
if (!$reflection->isInstantiable()) {
|
||||
return [];
|
||||
}
|
||||
// 'flags' is a public property with a default value in AbstractProtocol
|
||||
$instanceForFlags = $reflection->newInstanceWithoutConstructor();
|
||||
return $instanceForFlags->flags;
|
||||
} catch (\ReflectionException $e) {
|
||||
// Log or handle error if a class is problematic
|
||||
report($e);
|
||||
return [];
|
||||
}
|
||||
})
|
||||
->flatten()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识匹配合适的协议处理器类名
|
||||
*
|
||||
* @param string $flag 请求标识
|
||||
* @return string|null 协议类名或null
|
||||
*/
|
||||
public function matchProtocolClassName(string $flag): ?string
|
||||
{
|
||||
// 按照相反顺序,使最新定义的协议有更高优先级
|
||||
foreach (array_reverse($this->getProtocolClasses()) as $protocolClassString) {
|
||||
try {
|
||||
$reflection = new \ReflectionClass($protocolClassString);
|
||||
|
||||
if (!$reflection->isInstantiable() || !$reflection->isSubclassOf(AbstractProtocol::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 'flags' is a public property in AbstractProtocol
|
||||
$instanceForFlags = $reflection->newInstanceWithoutConstructor();
|
||||
$flags = $instanceForFlags->flags;
|
||||
|
||||
if (collect($flags)->contains(fn($f) => stripos($flag, (string) $f) !== false)) {
|
||||
return $protocolClassString; // 返回类名字符串
|
||||
}
|
||||
} catch (\ReflectionException $e) {
|
||||
report($e); // Consider logging this error
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识匹配合适的协议处理器实例 (原有逻辑,如果还需要的话)
|
||||
*
|
||||
* @param string $flag 请求标识
|
||||
* @param array $user 用户信息
|
||||
* @param array $servers 服务器列表
|
||||
* @param array $clientInfo 客户端信息
|
||||
* @return AbstractProtocol|null
|
||||
*/
|
||||
public function matchProtocol($flag, $user, $servers, $clientInfo = [])
|
||||
{
|
||||
$protocolClassName = $this->matchProtocolClassName($flag);
|
||||
if ($protocolClassName) {
|
||||
return $this->makeProtocolInstance($protocolClassName, [
|
||||
'user' => $user,
|
||||
'servers' => $servers,
|
||||
'clientName' => $clientInfo['name'] ?? null,
|
||||
'clientVersion' => $clientInfo['version'] ?? null
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建协议实例的通用方法,兼容不同版本的Laravel容器
|
||||
*
|
||||
* @param string $class 类名
|
||||
* @param array $parameters 构造参数
|
||||
* @return object 实例
|
||||
*/
|
||||
protected function makeProtocolInstance($class, array $parameters)
|
||||
{
|
||||
// Laravel's make method can accept an array of parameters as its second argument.
|
||||
// These will be used when resolving the class's dependencies.
|
||||
return $this->container->make($class, $parameters);
|
||||
}
|
||||
}
|
||||
140
Xboard/app/Support/Setting.php
Normal file
140
Xboard/app/Support/Setting.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use App\Models\Setting as SettingModel;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Contracts\Cache\Repository;
|
||||
|
||||
class Setting
|
||||
{
|
||||
const CACHE_KEY = 'admin_settings';
|
||||
|
||||
private Repository $cache;
|
||||
private ?array $loadedSettings = null; // 请求内缓存
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cache = Cache::store('redis');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置.
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
$this->load();
|
||||
return Arr::get($this->loadedSettings, strtolower($key), $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置配置信息.
|
||||
*/
|
||||
public function set(string $key, mixed $value = null): bool
|
||||
{
|
||||
SettingModel::createOrUpdate(strtolower($key), $value);
|
||||
$this->flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置到数据库.
|
||||
*/
|
||||
public function save(array $settings): bool
|
||||
{
|
||||
foreach ($settings as $key => $value) {
|
||||
SettingModel::createOrUpdate(strtolower($key), $value);
|
||||
}
|
||||
$this->flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除配置信息
|
||||
*/
|
||||
public function remove(string $key): bool
|
||||
{
|
||||
SettingModel::where('name', $key)->delete();
|
||||
$this->flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个设置项
|
||||
*/
|
||||
public function update(string $key, $value): bool
|
||||
{
|
||||
return $this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取配置项
|
||||
*/
|
||||
public function getBatch(array $keys): array
|
||||
{
|
||||
$this->load();
|
||||
$result = [];
|
||||
|
||||
foreach ($keys as $index => $item) {
|
||||
$isNumericIndex = is_numeric($index);
|
||||
$key = strtolower($isNumericIndex ? $item : $index);
|
||||
$default = $isNumericIndex ? config('v2board.' . $item) : (config('v2board.' . $key) ?? $item);
|
||||
|
||||
$result[$item] = Arr::get($this->loadedSettings, $key, $default);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有设置转换为数组
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$this->load();
|
||||
return $this->loadedSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载配置到请求内缓存
|
||||
*/
|
||||
private function load(): void
|
||||
{
|
||||
if ($this->loadedSettings !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$settings = $this->cache->rememberForever(self::CACHE_KEY, function (): array {
|
||||
return array_change_key_case(
|
||||
SettingModel::pluck('value', 'name')->toArray(),
|
||||
CASE_LOWER
|
||||
);
|
||||
});
|
||||
|
||||
// 处理JSON格式的值
|
||||
foreach ($settings as $key => $value) {
|
||||
if (is_string($value)) {
|
||||
$decoded = json_decode($value, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$settings[$key] = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->loadedSettings = $settings;
|
||||
} catch (\Throwable) {
|
||||
$this->loadedSettings = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空缓存
|
||||
*/
|
||||
private function flush(): void
|
||||
{
|
||||
$this->cache->forget(self::CACHE_KEY);
|
||||
$this->loadedSettings = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user