Files
datahub/backend/app/Entity/Parse/EntityParseFactory.php
T
2025-12-15 15:47:56 +08:00

323 lines
9.9 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Entity\Parse;
use App\Model\Platform;
use Hyperf\Amqp\Message\ConsumerMessageInterface;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Stringable\Str;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
use PhpAmqpLib\Message\AMQPMessage;
/**
* EntityParseFactory 工厂类
*
* 自动根据消息特征创建对应平台的 EntityParse 实例
* 使用静态方法调用,无需依赖注入
*
* @example
* // 基础用法
* use App\Entity\Parse\EntityParseFactory;
*
* $parser = EntityParseFactory::createFromMessage($message);
* $entity = $parser->entityMatch($message);
* $rawData = json_decode($message->getBody(), true);
* $entity = $parser->entityMap($rawData, $entity);
* $entity->save();
*
* // 指定实体类型(覆盖自动提取)
* $parser = EntityParseFactory::createFromMessage($message, 'order');
*
* 命名约定(推荐方式):
* - 创建符合命名约定的 Parser 类,工厂自动查找
* - 类名:{Entity}
* - 命名空间:App\Platform\{Platform}\EntityParse
* - 示例:App\Platform\Shopee\EntityParse\Order
*
* 平台名称来源:
* - 从 platforms 表中根据 platform_id 查询 name 字段
* - 示例:platform_id=25 -> name='Shopee' -> App\Platform\Shopee\EntityParse\Order
*
* 如果找不到对应的 Parser 类,将抛出 InvalidArgumentException
*
* 配置文件(可选):
* - 如需覆盖默认行为,可在 config/autoload/entity_parse.php 中配置
* - 配置格式:[platform_id => ['entity_type' => ParserClass]]
* - 支持通配符 '*' 匹配所有实体类型
*/
class EntityParseFactory
{
/**
* 平台与 Parser 类的映射表
* 格式:[platform_id => ['entity_type' => ParserClass]]
* 示例:[25 => ['order' => ShopeeOrderParser, 'product' => ShopeeProductParser]]
*
* @var array<int, array<string, class-string<EntityParse>>>
*/
private static array $registry = [];
/**
* 容器实例
*/
private static ?ContainerInterface $container = null;
/**
* 配置实例
*/
private static ?ConfigInterface $config = null;
/**
* 设置容器(由框架注入)
*
* @param ContainerInterface $container
* @return void
*/
public static function setContainer(ContainerInterface $container): void
{
self::$container = $container;
self::$config = $container->get(ConfigInterface::class);
self::loadConfig();
}
/**
* 从配置文件加载平台映射
*
* @return void
*/
private static function loadConfig(): void
{
if (!self::$config) {
return;
}
$parsers = self::$config->get('entity_parse.platforms', []);
foreach ($parsers as $platform => $entities) {
foreach ($entities as $entityType => $parserClass) {
self::register($platform, $entityType, $parserClass);
}
}
}
/**
* 根据消息自动创建 Parser 实例(静态方法)
*
* @param AMQPMessage $message
* @param string|null $entityType 实体类型(可选,如果不指定则从 payload 中提取)
* @return EntityParseInterface
* @throws InvalidArgumentException
*/
public static function createFromMessage(
AMQPMessage $message,
?string $entityType = null
): EntityParseInterface {
// 1. 从消息体中解析数据
$data = json_decode($message->getBody(), true);
if (!is_array($data)) {
throw new InvalidArgumentException('Invalid message body: expected JSON array');
}
// 2. 从 data 中提取 platform_id
if (!isset($data['platform_id'])) {
throw new InvalidArgumentException('Missing required field: platform_id');
}
$platformId = (int) $data['platform_id'];
// 3. 如果未指定实体类型,从 routing key 中提取
if ($entityType === null) {
$entityType = self::extractEntityTypeFromData($data, $message);
}
// 4. 获取对应的 Parser 类
$parserClass = self::resolveParserClass($platformId, $entityType);
// 5. 创建并返回 Parser 实例,传递解析后的数据和消息对象
return $parserClass::create($data, $message);
}
/**
* 从路由键中提取实体类型
*
* @param array $data
* @param AMQPMessage $message
* @return string
*/
private static function extractEntityTypeFromData(array $data, AMQPMessage $message): string
{
// 优先从 routing key 中提取
$routingKey = $message->getRoutingKey();
$entityType = Str::of($routingKey)
->before('.')
->lower()
->toString();
if (!empty($entityType)) {
return $entityType;
}
throw new InvalidArgumentException("Cannot extract entity type from routing key: {$routingKey}");
}
/**
* 解析 Parser 类名
*
* 优先级:
* 1. 手动注册的映射
* 2. 配置文件中的映射
* 3. 命名约定自动查找
*
* @param int $platformId
* @param string $entityType
* @return class-string<EntityParse>
* @throws InvalidArgumentException
*/
private static function resolveParserClass(int $platformId, string $entityType): string
{
// 1. 检查手动注册的映射
if (isset(self::$registry[$platformId][$entityType])) {
return self::$registry[$platformId][$entityType];
}
// 2. 检查通配符映射(支持 * 匹配所有实体类型)
if (isset(self::$registry[$platformId]['*'])) {
return self::$registry[$platformId]['*'];
}
// 3. 使用命名约定自动查找
$parserClass = self::findByConvention($platformId, $entityType);
if ($parserClass) {
// 自动注册以提高后续性能
self::register($platformId, $entityType, $parserClass);
return $parserClass;
}
throw new InvalidArgumentException(
"Cannot find parser for platform_id '{$platformId}' and entity '{$entityType}'. " .
"Please register it using EntityParseFactory::register() or create a parser class following the naming convention."
);
}
/**
* 根据命名约定查找 Parser 类
*
* 命名约定:
* - 类名:{Entity}
* - 命名空间:App\Platform\{Platform}\EntityParse
*
* 示例:
* - platform_id: 25 (Shopee), entity: order -> App\Platform\Shopee\EntityParse\Order
* - platform_id: 2 (Tmall), entity: product -> App\Platform\Tmall\EntityParse\Product
*
* @param int $platformId
* @param string $entityType
* @return class-string<EntityParse>|null
*/
private static function findByConvention(int $platformId, string $entityType): ?string
{
// 从数据库查询平台信息
$platform = Platform::find($platformId);
if (!$platform || !isset($platform->name)) {
return null;
}
// 使用平台名称构建类名(处理多词平台名,如 "Amazon Japan" -> "AmazonJapan"
$platformPascal = Str::of($platform->name)
->replace(' ', '') // 移除空格
->studly() // 转换为 StudlyCase
->toString();
$entityPascal = Str::of($entityType)->studly()->toString();
// 标准命名:App\Platform\Shopee\EntityParse\Order
$class = "App\\Platform\\{$platformPascal}\\EntityParse\\{$entityPascal}";
if (class_exists($class) && is_subclass_of($class, EntityParse::class)) {
return $class;
}
return null;
}
/**
* 手动注册平台与 Parser 的映射关系
*
* @param int $platformId 平台 ID(如:25 表示 Shopee
* @param string $entityType 实体类型(小写),使用 '*' 表示匹配所有实体类型
* @param class-string<EntityParse> $parserClass Parser 类名
* @return void
* @throws InvalidArgumentException
*/
public static function register(int $platformId, string $entityType, string $parserClass): void
{
// 验证 Parser 类是否存在
if (!class_exists($parserClass)) {
throw new InvalidArgumentException("Parser class '{$parserClass}' does not exist");
}
// 验证 Parser 类是否继承自 EntityParse
if (!is_subclass_of($parserClass, EntityParse::class)) {
throw new InvalidArgumentException(
"Parser class '{$parserClass}' must extend " . EntityParse::class
);
}
// 注册映射
self::$registry[$platformId][$entityType] = $parserClass;
}
/**
* 批量注册平台映射
*
* @param array<int, array<string, class-string<EntityParse>>> $mappings
* @return void
*/
public static function registerBatch(array $mappings): void
{
foreach ($mappings as $platformId => $entities) {
foreach ($entities as $entityType => $parserClass) {
self::register($platformId, $entityType, $parserClass);
}
}
}
/**
* 获取所有已注册的映射
*
* @return array<int, array<string, class-string<EntityParse>>>
*/
public static function getRegistry(): array
{
return self::$registry;
}
/**
* 清空所有注册的映射(主要用于测试)
*
* @return void
*/
public static function clearRegistry(): void
{
self::$registry = [];
}
/**
* 检查指定平台和实体类型的 Parser 是否已注册
*
* @param int $platformId
* @param string $entityType
* @return bool
*/
public static function hasParser(int $platformId, string $entityType): bool
{
return isset(self::$registry[$platformId][$entityType])
|| isset(self::$registry[$platformId]['*']);
}
}