数据中心同步

This commit is contained in:
乘风
2026-01-05 10:16:20 +08:00
parent 0457528dd0
commit ba0ebcf273
98 changed files with 28583 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
<?php
namespace app\service\DataSource\Adapter;
use app\service\DataSource\DataSourceAdapterInterface;
use app\utils\LoggerHelper;
use MongoDB\Client;
use MongoDB\Driver\Exception\Exception as MongoDBException;
/**
* MongoDB 数据源适配器
*
* 职责:
* - 封装 MongoDB 数据库连接和查询操作
* - 实现 DataSourceAdapterInterface 接口
*/
class MongoDBAdapter implements DataSourceAdapterInterface
{
private ?Client $client = null;
private ?\MongoDB\Database $database = null;
private string $type = 'mongodb';
private string $databaseName = '';
/**
* 建立数据库连接
*
* @param array<string, mixed> $config 数据源配置
* @return bool 是否连接成功
*/
public function connect(array $config): bool
{
try {
$host = $config['host'] ?? '127.0.0.1';
$port = (int)($config['port'] ?? 27017);
$this->databaseName = $config['database'] ?? '';
$username = $config['username'] ?? '';
$password = $config['password'] ?? '';
$authSource = $config['auth_source'] ?? $this->databaseName;
// 构建 DSN
$dsn = "mongodb://";
if (!empty($username) && !empty($password)) {
$dsn .= urlencode($username) . ':' . urlencode($password) . '@';
}
$dsn .= "{$host}:{$port}";
if (!empty($this->databaseName)) {
$dsn .= "/{$this->databaseName}";
}
if (!empty($authSource)) {
$dsn .= "?authSource=" . urlencode($authSource);
}
// MongoDB 连接选项
$options = [];
if (isset($config['options'])) {
$options = array_filter($config['options'], function ($value) {
return $value !== '' && $value !== null;
});
}
// 设置超时选项
if (!isset($options['connectTimeoutMS'])) {
$options['connectTimeoutMS'] = ($config['timeout'] ?? 10) * 1000;
}
if (!isset($options['socketTimeoutMS'])) {
$options['socketTimeoutMS'] = ($config['timeout'] ?? 10) * 1000;
}
$this->client = new Client($dsn, $options);
// 选择数据库
if (!empty($this->databaseName)) {
$this->database = $this->client->selectDatabase($this->databaseName);
}
// 测试连接
$this->client->getManager()->selectServer();
LoggerHelper::logBusiness('mongodb_adapter_connected', [
'host' => $host,
'port' => $port,
'database' => $this->databaseName,
]);
return true;
} catch (MongoDBException $e) {
LoggerHelper::logError($e, [
'component' => 'MongoDBAdapter',
'action' => 'connect',
'config' => array_merge($config, ['password' => '***']), // 隐藏密码
]);
return false;
}
}
/**
* 关闭数据库连接
*
* @return void
*/
public function disconnect(): void
{
if ($this->client !== null) {
$this->client = null;
$this->database = null;
LoggerHelper::logBusiness('mongodb_adapter_disconnected', []);
}
}
/**
* 测试连接是否有效
*
* @return bool 连接是否有效
*/
public function isConnected(): bool
{
if ($this->client === null) {
return false;
}
try {
// 执行 ping 命令测试连接
$adminDb = $this->client->selectDatabase('admin');
$adminDb->command(['ping' => 1]);
return true;
} catch (MongoDBException $e) {
LoggerHelper::logError($e, [
'component' => 'MongoDBAdapter',
'action' => 'isConnected',
]);
return false;
}
}
/**
* 执行查询(返回多条记录)
*
* 注意:对于 MongoDB$sql 参数表示集合名称,$params 是一个包含 'filter' 和 'options' 的数组
*
* @param string $sql 集合名称MongoDB 中相当于表名)
* @param array<string, mixed> $params 查询参数,格式:['filter' => [...], 'options' => [...]]
* @return array<array<string, mixed>> 查询结果数组
*/
public function query(string $sql, array $params = []): array
{
if ($this->database === null) {
throw new \RuntimeException('数据库连接未建立或未选择数据库');
}
try {
$collection = $sql; // $sql 参数在 MongoDB 中表示集合名
$filter = $params['filter'] ?? [];
$options = $params['options'] ?? [];
$cursor = $this->database->selectCollection($collection)->find($filter, $options);
$results = [];
foreach ($cursor as $document) {
$results[] = $this->convertMongoDocumentToArray($document);
}
LoggerHelper::logBusiness('mongodb_query_executed', [
'collection' => $collection,
'filter' => $filter,
'result_count' => count($results),
]);
return $results;
} catch (MongoDBException $e) {
LoggerHelper::logError($e, [
'component' => 'MongoDBAdapter',
'action' => 'query',
'collection' => $sql,
'params' => $params,
]);
throw $e;
}
}
/**
* 执行查询(返回单条记录)
*
* 注意:对于 MongoDB$sql 参数表示集合名称,$params 是一个包含 'filter' 和 'options' 的数组
*
* @param string $sql 集合名称
* @param array<string, mixed> $params 查询参数,格式:['filter' => [...], 'options' => [...]]
* @return array<string, mixed>|null 查询结果(单条记录)或 null
*/
public function queryOne(string $sql, array $params = []): ?array
{
if ($this->database === null) {
throw new \RuntimeException('数据库连接未建立或未选择数据库');
}
try {
$collection = $sql; // $sql 参数在 MongoDB 中表示集合名
$filter = $params['filter'] ?? [];
$options = $params['options'] ?? [];
$document = $this->database->selectCollection($collection)->findOne($filter, $options);
if ($document === null) {
return null;
}
LoggerHelper::logBusiness('mongodb_query_one_executed', [
'collection' => $collection,
'filter' => $filter,
'has_result' => true,
]);
return $this->convertMongoDocumentToArray($document);
} catch (MongoDBException $e) {
LoggerHelper::logError($e, [
'component' => 'MongoDBAdapter',
'action' => 'queryOne',
'collection' => $sql,
'params' => $params,
]);
throw $e;
}
}
/**
* 批量查询(分页查询,用于大数据量场景)
*
* 注意:对于 MongoDB$sql 参数表示集合名称,$params 是一个包含 'filter' 和 'options' 的数组
*
* @param string $sql 集合名称
* @param array<string, mixed> $params 查询参数,格式:['filter' => [...], 'options' => [...]]
* @param int $offset 偏移量
* @param int $limit 每页数量
* @return array<array<string, mixed>> 查询结果数组
*/
public function queryBatch(string $sql, array $params = [], int $offset = 0, int $limit = 1000): array
{
if ($this->database === null) {
throw new \RuntimeException('数据库连接未建立或未选择数据库');
}
try {
$collection = $sql; // $sql 参数在 MongoDB 中表示集合名
$filter = $params['filter'] ?? [];
$options = $params['options'] ?? [];
// 设置分页选项
$options['skip'] = $offset;
$options['limit'] = $limit;
$cursor = $this->database->selectCollection($collection)->find($filter, $options);
$results = [];
foreach ($cursor as $document) {
$results[] = $this->convertMongoDocumentToArray($document);
}
LoggerHelper::logBusiness('mongodb_query_batch_executed', [
'collection' => $collection,
'offset' => $offset,
'limit' => $limit,
'result_count' => count($results),
]);
return $results;
} catch (MongoDBException $e) {
LoggerHelper::logError($e, [
'component' => 'MongoDBAdapter',
'action' => 'queryBatch',
'collection' => $sql,
'params' => $params,
'offset' => $offset,
'limit' => $limit,
]);
throw $e;
}
}
/**
* 获取数据源类型
*
* @return string 数据源类型
*/
public function getType(): string
{
return $this->type;
}
/**
* 将 MongoDB 文档转换为数组
*
* @param mixed $document MongoDB 文档对象
* @return array<string, mixed> 数组格式的数据
*/
private function convertMongoDocumentToArray($document): array
{
if (is_array($document)) {
return $document;
}
// MongoDB\BSON\Document 或 MongoDB\Model\BSONDocument
if (method_exists($document, 'toArray')) {
return $document->toArray();
}
// 转换为数组
return json_decode(json_encode($document), true) ?? [];
}
}

View File

@@ -0,0 +1,234 @@
<?php
namespace app\service\DataSource\Adapter;
use app\service\DataSource\DataSourceAdapterInterface;
use app\utils\LoggerHelper;
use PDO;
use PDOException;
/**
* MySQL 数据源适配器
*
* 职责:
* - 封装 MySQL 数据库连接和查询操作
* - 实现 DataSourceAdapterInterface 接口
*/
class MySQLAdapter implements DataSourceAdapterInterface
{
private ?PDO $connection = null;
private string $type = 'mysql';
/**
* 建立数据库连接
*
* @param array<string, mixed> $config 数据源配置
* @return bool 是否连接成功
*/
public function connect(array $config): bool
{
try {
$host = $config['host'] ?? '127.0.0.1';
$port = $config['port'] ?? 3306;
$database = $config['database'] ?? '';
$username = $config['username'] ?? '';
$password = $config['password'] ?? '';
$charset = $config['charset'] ?? 'utf8mb4';
// 构建 DSN
$dsn = "mysql:host={$host};port={$port};dbname={$database};charset={$charset}";
// PDO 选项
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 禁用预处理语句模拟
PDO::ATTR_PERSISTENT => $config['persistent'] ?? false, // 是否持久连接
PDO::ATTR_TIMEOUT => $config['timeout'] ?? 10, // 连接超时
];
$this->connection = new PDO($dsn, $username, $password, $options);
LoggerHelper::logBusiness('mysql_adapter_connected', [
'host' => $host,
'port' => $port,
'database' => $database,
]);
return true;
} catch (PDOException $e) {
LoggerHelper::logError($e, [
'component' => 'MySQLAdapter',
'action' => 'connect',
'config' => array_merge($config, ['password' => '***']), // 隐藏密码
]);
return false;
}
}
/**
* 关闭数据库连接
*
* @return void
*/
public function disconnect(): void
{
if ($this->connection !== null) {
$this->connection = null;
LoggerHelper::logBusiness('mysql_adapter_disconnected', []);
}
}
/**
* 测试连接是否有效
*
* @return bool 连接是否有效
*/
public function isConnected(): bool
{
if ($this->connection === null) {
return false;
}
try {
// 执行简单查询测试连接
$this->connection->query('SELECT 1');
return true;
} catch (PDOException $e) {
LoggerHelper::logError($e, [
'component' => 'MySQLAdapter',
'action' => 'isConnected',
]);
return false;
}
}
/**
* 执行查询(返回多条记录)
*
* @param string $sql SQL 查询语句
* @param array<string, mixed> $params 查询参数(绑定参数)
* @return array<array<string, mixed>> 查询结果数组
*/
public function query(string $sql, array $params = []): array
{
if ($this->connection === null) {
throw new \RuntimeException('数据库连接未建立');
}
try {
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
LoggerHelper::logBusiness('mysql_query_executed', [
'sql' => $sql,
'params_count' => count($params),
'result_count' => count($results),
]);
return $results;
} catch (PDOException $e) {
LoggerHelper::logError($e, [
'component' => 'MySQLAdapter',
'action' => 'query',
'sql' => $sql,
'params' => $params,
]);
throw $e;
}
}
/**
* 执行查询(返回单条记录)
*
* @param string $sql SQL 查询语句
* @param array<string, mixed> $params 查询参数
* @return array<string, mixed>|null 查询结果(单条记录)或 null
*/
public function queryOne(string $sql, array $params = []): ?array
{
if ($this->connection === null) {
throw new \RuntimeException('数据库连接未建立');
}
try {
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
LoggerHelper::logBusiness('mysql_query_one_executed', [
'sql' => $sql,
'params_count' => count($params),
'has_result' => $result !== false,
]);
return $result !== false ? $result : null;
} catch (PDOException $e) {
LoggerHelper::logError($e, [
'component' => 'MySQLAdapter',
'action' => 'queryOne',
'sql' => $sql,
'params' => $params,
]);
throw $e;
}
}
/**
* 批量查询(分页查询,用于大数据量场景)
*
* @param string $sql SQL 查询语句(需要包含 LIMIT 和 OFFSET或由适配器自动添加
* @param array<string, mixed> $params 查询参数
* @param int $offset 偏移量
* @param int $limit 每页数量
* @return array<array<string, mixed>> 查询结果数组
*/
public function queryBatch(string $sql, array $params = [], int $offset = 0, int $limit = 1000): array
{
if ($this->connection === null) {
throw new \RuntimeException('数据库连接未建立');
}
try {
// 如果 SQL 中已包含 LIMIT则直接使用否则自动添加
if (stripos($sql, 'LIMIT') === false) {
$sql .= " LIMIT {$limit} OFFSET {$offset}";
}
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
LoggerHelper::logBusiness('mysql_query_batch_executed', [
'sql' => $sql,
'offset' => $offset,
'limit' => $limit,
'result_count' => count($results),
]);
return $results;
} catch (PDOException $e) {
LoggerHelper::logError($e, [
'component' => 'MySQLAdapter',
'action' => 'queryBatch',
'sql' => $sql,
'params' => $params,
'offset' => $offset,
'limit' => $limit,
]);
throw $e;
}
}
/**
* 获取数据源类型
*
* @return string 数据源类型
*/
public function getType(): string
{
return $this->type;
}
}