Skip to content

Commit 16d60d7

Browse files
committed
链接成功
1 parent 3e6d364 commit 16d60d7

7 files changed

Lines changed: 914 additions & 9 deletions

File tree

app/Protocol/ConnectionContext.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Swoole\Coroutine\Socket;
88
use Swoole\Lock;
9+
use App\Proxy\Client\ClientType;
910

1011
class ConnectionContext
1112
{
@@ -26,6 +27,9 @@ class ConnectionContext
2627
private ?string $database = null;
2728
private bool $sslRequested = false;
2829
private string $authPluginData = '';
30+
private ClientType $clientType = ClientType::UNKNOWN;
31+
private ?string $clientVersion = null;
32+
private array $clientCapabilities = [];
2933

3034
public function __construct(int $clientId, string $clientIp, int $clientPort)
3135
{
@@ -199,6 +203,36 @@ public function setAuthPluginData(string $authPluginData): void
199203
$this->authPluginData = $authPluginData;
200204
}
201205

206+
public function getClientType(): ClientType
207+
{
208+
return $this->clientType;
209+
}
210+
211+
public function setClientType(ClientType $clientType): void
212+
{
213+
$this->clientType = $clientType;
214+
}
215+
216+
public function getClientVersion(): ?string
217+
{
218+
return $this->clientVersion;
219+
}
220+
221+
public function setClientVersion(?string $clientVersion): void
222+
{
223+
$this->clientVersion = $clientVersion;
224+
}
225+
226+
public function getClientCapabilities(): array
227+
{
228+
return $this->clientCapabilities;
229+
}
230+
231+
public function setClientCapabilities(array $capabilities): void
232+
{
233+
$this->clientCapabilities = $capabilities;
234+
}
235+
202236
public function __toString(): string
203237
{
204238
return sprintf('%s:%d', $this->clientIp, $this->clientPort);
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Proxy\Client;
6+
7+
use App\Protocol\ConnectionContext;
8+
use Hyperf\Logger\LoggerFactory;
9+
use Psr\Log\LoggerInterface;
10+
11+
/**
12+
* 客户端检测器
13+
* 根据客户端的特征信息识别客户端类型
14+
*/
15+
class ClientDetector
16+
{
17+
private LoggerInterface $logger;
18+
19+
public function __construct(LoggerFactory $loggerFactory)
20+
{
21+
$this->logger = $loggerFactory->get('client_detector');
22+
}
23+
24+
/**
25+
* 从握手信息检测客户端类型
26+
*/
27+
public function detectFromHandshake(ConnectionContext $context, array $handshakeData): void
28+
{
29+
$capabilities = $handshakeData['capabilities'] ?? 0;
30+
$charset = $handshakeData['charset'] ?? 0;
31+
32+
$this->logger->info('开始从握手信息检测客户端类型', [
33+
'client_id' => $context->getClientId(),
34+
'capabilities' => sprintf('0x%08x', $capabilities),
35+
'charset' => $charset,
36+
]);
37+
38+
$detectedType = $this->analyzeCapabilities($capabilities, $charset);
39+
40+
if ($detectedType !== ClientType::UNKNOWN) {
41+
$context->setClientType($detectedType);
42+
$this->logger->info('从握手信息识别到客户端类型', [
43+
'client_id' => $context->getClientId(),
44+
'client_type' => $detectedType->value,
45+
'client_description' => $detectedType->getDescription(),
46+
'detection_method' => 'handshake_capabilities',
47+
]);
48+
}
49+
50+
$context->setClientCapabilities([
51+
'capabilities' => $capabilities,
52+
'charset' => $charset,
53+
]);
54+
}
55+
56+
/**
57+
* 从SQL查询检测客户端类型和版本
58+
*/
59+
public function detectFromQuery(ConnectionContext $context, string $sql): void
60+
{
61+
// 如果已经识别出客户端类型,跳过
62+
if ($context->getClientType() !== ClientType::UNKNOWN) {
63+
return;
64+
}
65+
66+
$this->logger->debug('开始从SQL查询检测客户端类型', [
67+
'client_id' => $context->getClientId(),
68+
'sql_preview' => substr($sql, 0, 100),
69+
]);
70+
71+
// 检测Java Connector/J的注释
72+
if (preg_match('/\/\* mysql-connector-j-([\d.]+).*?\*\//i', $sql, $matches)) {
73+
$context->setClientType(ClientType::JAVA_CONNECTOR);
74+
$context->setClientVersion($matches[1]);
75+
$this->logger->info('从查询注释识别到 Java Connector/J', [
76+
'client_id' => $context->getClientId(),
77+
'version' => $matches[1],
78+
'detection_method' => 'query_comment',
79+
'full_comment' => $matches[0],
80+
]);
81+
return;
82+
}
83+
84+
// 检测PHP PDO的特征(通常没有特殊注释,但可能有特定的查询模式)
85+
if ($this->isLikelyPhpPdo($sql)) {
86+
$context->setClientType(ClientType::PHP_PDO);
87+
$this->logger->info('从查询模式识别到 PHP PDO', [
88+
'client_id' => $context->getClientId(),
89+
'detection_method' => 'query_pattern',
90+
]);
91+
return;
92+
}
93+
94+
// 检测MySQL原生客户端
95+
if ($this->isLikelyMySQLClient($sql)) {
96+
$context->setClientType(ClientType::MYSQL_CLIENT);
97+
$this->logger->info('从查询模式识别到 MySQL 原生客户端', [
98+
'client_id' => $context->getClientId(),
99+
'detection_method' => 'query_pattern',
100+
]);
101+
}
102+
}
103+
104+
/**
105+
* 从连接属性检测客户端类型
106+
*/
107+
public function detectFromConnectionAttributes(ConnectionContext $context, array $attributes): void
108+
{
109+
if ($context->getClientType() !== ClientType::UNKNOWN) {
110+
return;
111+
}
112+
113+
$this->logger->debug('开始从连接属性检测客户端类型', [
114+
'client_id' => $context->getClientId(),
115+
'attributes' => $attributes,
116+
]);
117+
118+
// 检查程序名
119+
$programName = $attributes['_client_name'] ?? $attributes['program_name'] ?? '';
120+
121+
if (stripos($programName, 'mysql-connector-java') !== false) {
122+
$context->setClientType(ClientType::JAVA_CONNECTOR);
123+
$this->logger->info('从连接属性识别到 Java Connector', [
124+
'client_id' => $context->getClientId(),
125+
'program_name' => $programName,
126+
'detection_method' => 'connection_attributes',
127+
]);
128+
} elseif (stripos($programName, 'pdo') !== false) {
129+
$context->setClientType(ClientType::PHP_PDO);
130+
$this->logger->info('从连接属性识别到 PHP PDO', [
131+
'client_id' => $context->getClientId(),
132+
'program_name' => $programName,
133+
'detection_method' => 'connection_attributes',
134+
]);
135+
}
136+
}
137+
138+
/**
139+
* 分析客户端能力标志来识别客户端类型
140+
*/
141+
private function analyzeCapabilities(int $capabilities, int $charset): ClientType
142+
{
143+
// Java Connector/J 的特征:
144+
// - 通常支持 PLUGIN_AUTH
145+
// - 字符集通常是 utf8mb4 (45) 或更高
146+
if (($capabilities & 0x00080000) && // CLIENT_PLUGIN_AUTH
147+
($charset >= 45 && $charset <= 255)) {
148+
return ClientType::JAVA_CONNECTOR;
149+
}
150+
151+
// PHP PDO 的特征:
152+
// - 支持 MULTI_STATEMENTS
153+
// - 字符集通常是 utf8 (33) 或 utf8mb4 (45)
154+
if (($capabilities & 0x00010000) && // CLIENT_MULTI_STATEMENTS
155+
($charset >= 33 && $charset <= 45)) {
156+
return ClientType::PHP_PDO;
157+
}
158+
159+
// MySQL 原生客户端的特征:
160+
// - 支持基础功能,但通常不设置高级能力标志
161+
if (($capabilities & 0x00000001) && // CLIENT_LONG_PASSWORD
162+
!($capabilities & 0x00080000)) { // 没有 CLIENT_PLUGIN_AUTH
163+
return ClientType::MYSQL_CLIENT;
164+
}
165+
166+
return ClientType::UNKNOWN;
167+
}
168+
169+
/**
170+
* 判断是否可能是PHP PDO
171+
*/
172+
private function isLikelyPhpPdo(string $sql): bool
173+
{
174+
// PHP PDO 通常发送简单的查询,没有特殊的注释
175+
// 这里可以根据查询模式进行判断
176+
return preg_match('/^SELECT\s+@@/i', trim($sql)) === 1;
177+
}
178+
179+
/**
180+
* 判断是否可能是MySQL原生客户端
181+
*/
182+
private function isLikelyMySQLClient(string $sql): bool
183+
{
184+
// MySQL客户端通常发送简单的命令
185+
return preg_match('/^(SELECT|SHOW|SET)\s/i', trim($sql)) === 1;
186+
}
187+
188+
/**
189+
* 获取客户端类型统计信息
190+
*/
191+
public function getClientTypeStats(): array
192+
{
193+
// 这里可以实现客户端类型统计逻辑
194+
return [
195+
'total_clients' => 0,
196+
'type_breakdown' => [],
197+
];
198+
}
199+
}

app/Proxy/Client/ClientType.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Proxy\Client;
6+
7+
/**
8+
* 客户端类型枚举
9+
*/
10+
enum ClientType: string
11+
{
12+
case JAVA_CONNECTOR = 'java_connector';
13+
case PHP_PDO = 'php_pdo';
14+
case MYSQL_CLIENT = 'mysql_client';
15+
case UNKNOWN = 'unknown';
16+
17+
/**
18+
* 获取客户端类型的描述
19+
*/
20+
public function getDescription(): string
21+
{
22+
return match ($this) {
23+
self::JAVA_CONNECTOR => 'Java MySQL Connector/J',
24+
self::PHP_PDO => 'PHP PDO',
25+
self::MYSQL_CLIENT => 'MySQL 原生客户端',
26+
self::UNKNOWN => '未知客户端',
27+
};
28+
}
29+
}

0 commit comments

Comments
 (0)