Files
datahub/backend/app/Entity/Parse/EntityParseFactory.php
T

323 lines
9.9 KiB
PHP
Raw Normal View History

2025-11-27 13:40:58 +08:00
<?php
declare(strict_types=1);
namespace App\Entity\Parse;
2025-12-15 15:47:56 +08:00
use App\Model\Platform;
2025-11-27 13:40:58 +08:00
use Hyperf\Amqp\Message\ConsumerMessageInterface;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Stringable\Str;
use InvalidArgumentException;
use Psr\Container\ContainerInterface;
2025-11-27 15:03:25 +08:00
use PhpAmqpLib\Message\AMQPMessage;
2025-11-27 13:40:58 +08:00
/**
* 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 类,工厂自动查找
2025-12-15 15:47:56 +08:00
* - 类名:{Entity}
* - 命名空间:App\Platform\{Platform}\EntityParse
* - 示例:App\Platform\Shopee\EntityParse\Order
2025-11-27 13:40:58 +08:00
*
2025-12-15 15:47:56 +08:00
* 平台名称来源:
* - 从 platforms 表中根据 platform_id 查询 name 字段
* - 示例:platform_id=25 -> name='Shopee' -> App\Platform\Shopee\EntityParse\Order
2025-11-27 13:40:58 +08:00
*
* 如果找不到对应的 Parser 类,将抛出 InvalidArgumentException
*
* 配置文件(可选):
* - 如需覆盖默认行为,可在 config/autoload/entity_parse.php 中配置
2025-12-15 15:47:56 +08:00
* - 配置格式:[platform_id => ['entity_type' => ParserClass]]
2025-11-27 13:40:58 +08:00
* - 支持通配符 '*' 匹配所有实体类型
*/
class EntityParseFactory
{
/**
* 平台与 Parser 类的映射表
2025-12-15 15:47:56 +08:00
* 格式:[platform_id => ['entity_type' => ParserClass]]
* 示例:[25 => ['order' => ShopeeOrderParser, 'product' => ShopeeProductParser]]
2025-11-27 13:40:58 +08:00
*
2025-12-15 15:47:56 +08:00
* @var array<int, array<string, class-string<EntityParse>>>
2025-11-27 13:40:58 +08:00
*/
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 实例(静态方法)
*
2025-11-27 15:03:25 +08:00
* @param AMQPMessage $message
* @param string|null $entityType 实体类型(可选,如果不指定则从 payload 中提取)
2025-11-27 13:40:58 +08:00
* @return EntityParseInterface
* @throws InvalidArgumentException
*/
public static function createFromMessage(
2025-11-27 15:03:25 +08:00
AMQPMessage $message,
2025-11-27 13:40:58 +08:00
?string $entityType = null
): EntityParseInterface {
2025-11-27 15:03:25 +08:00
// 1. 从消息体中解析数据
$data = json_decode($message->getBody(), true);
2025-11-27 13:40:58 +08:00
2025-11-27 15:03:25 +08:00
if (!is_array($data)) {
throw new InvalidArgumentException('Invalid message body: expected JSON array');
}
2025-12-15 15:47:56 +08:00
// 2. 从 data 中提取 platform_id
if (!isset($data['platform_id'])) {
throw new InvalidArgumentException('Missing required field: platform_id');
}
$platformId = (int) $data['platform_id'];
2025-11-27 15:03:25 +08:00
2025-12-15 15:47:56 +08:00
// 3. 如果未指定实体类型,从 routing key 中提取
2025-11-27 13:40:58 +08:00
if ($entityType === null) {
2025-11-27 15:03:25 +08:00
$entityType = self::extractEntityTypeFromData($data, $message);
2025-11-27 13:40:58 +08:00
}
2025-11-27 15:03:25 +08:00
// 4. 获取对应的 Parser 类
2025-12-15 15:47:56 +08:00
$parserClass = self::resolveParserClass($platformId, $entityType);
2025-11-27 13:40:58 +08:00
2025-12-11 16:38:29 +08:00
// 5. 创建并返回 Parser 实例,传递解析后的数据和消息对象
return $parserClass::create($data, $message);
2025-11-27 13:40:58 +08:00
}
/**
2025-12-15 15:47:56 +08:00
* 从路由键中提取实体类型
2025-11-27 13:40:58 +08:00
*
2025-11-27 15:03:25 +08:00
* @param array $data
* @param AMQPMessage $message
2025-11-27 13:40:58 +08:00
* @return string
*/
2025-11-27 15:03:25 +08:00
private static function extractEntityTypeFromData(array $data, AMQPMessage $message): string
2025-11-27 13:40:58 +08:00
{
2025-11-27 15:03:25 +08:00
// 优先从 routing key 中提取
2025-11-27 13:40:58 +08:00
$routingKey = $message->getRoutingKey();
$entityType = Str::of($routingKey)
->before('.')
->lower()
->toString();
2025-11-27 15:03:25 +08:00
if (!empty($entityType)) {
return $entityType;
2025-11-27 13:40:58 +08:00
}
2025-11-27 15:03:25 +08:00
throw new InvalidArgumentException("Cannot extract entity type from routing key: {$routingKey}");
2025-11-27 13:40:58 +08:00
}
/**
* 解析 Parser 类名
*
* 优先级:
* 1. 手动注册的映射
* 2. 配置文件中的映射
* 3. 命名约定自动查找
*
2025-12-15 15:47:56 +08:00
* @param int $platformId
2025-11-27 13:40:58 +08:00
* @param string $entityType
* @return class-string<EntityParse>
* @throws InvalidArgumentException
*/
2025-12-15 15:47:56 +08:00
private static function resolveParserClass(int $platformId, string $entityType): string
2025-11-27 13:40:58 +08:00
{
// 1. 检查手动注册的映射
2025-12-15 15:47:56 +08:00
if (isset(self::$registry[$platformId][$entityType])) {
return self::$registry[$platformId][$entityType];
2025-11-27 13:40:58 +08:00
}
// 2. 检查通配符映射(支持 * 匹配所有实体类型)
2025-12-15 15:47:56 +08:00
if (isset(self::$registry[$platformId]['*'])) {
return self::$registry[$platformId]['*'];
2025-11-27 13:40:58 +08:00
}
// 3. 使用命名约定自动查找
2025-12-15 15:47:56 +08:00
$parserClass = self::findByConvention($platformId, $entityType);
2025-11-27 13:40:58 +08:00
if ($parserClass) {
// 自动注册以提高后续性能
2025-12-15 15:47:56 +08:00
self::register($platformId, $entityType, $parserClass);
2025-11-27 13:40:58 +08:00
return $parserClass;
}
throw new InvalidArgumentException(
2025-12-15 15:47:56 +08:00
"Cannot find parser for platform_id '{$platformId}' and entity '{$entityType}'. " .
2025-11-27 13:40:58 +08:00
"Please register it using EntityParseFactory::register() or create a parser class following the naming convention."
);
}
/**
* 根据命名约定查找 Parser 类
*
* 命名约定:
2025-12-09 15:45:03 +08:00
* - 类名:{Entity}
* - 命名空间:App\Platform\{Platform}\EntityParse
2025-11-27 13:40:58 +08:00
*
* 示例:
2025-12-15 15:47:56 +08:00
* - platform_id: 25 (Shopee), entity: order -> App\Platform\Shopee\EntityParse\Order
* - platform_id: 2 (Tmall), entity: product -> App\Platform\Tmall\EntityParse\Product
2025-11-27 13:40:58 +08:00
*
2025-12-15 15:47:56 +08:00
* @param int $platformId
2025-11-27 13:40:58 +08:00
* @param string $entityType
* @return class-string<EntityParse>|null
*/
2025-12-15 15:47:56 +08:00
private static function findByConvention(int $platformId, string $entityType): ?string
2025-11-27 13:40:58 +08:00
{
2025-12-15 15:47:56 +08:00
// 从数据库查询平台信息
$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();
2025-11-27 13:40:58 +08:00
2025-12-09 15:45:03 +08:00
// 标准命名:App\Platform\Shopee\EntityParse\Order
$class = "App\\Platform\\{$platformPascal}\\EntityParse\\{$entityPascal}";
2025-11-27 13:40:58 +08:00
2025-12-09 15:45:03 +08:00
if (class_exists($class) && is_subclass_of($class, EntityParse::class)) {
return $class;
2025-11-27 13:40:58 +08:00
}
return null;
}
/**
* 手动注册平台与 Parser 的映射关系
*
2025-12-15 15:47:56 +08:00
* @param int $platformId 平台 ID(如:25 表示 Shopee
2025-11-27 13:40:58 +08:00
* @param string $entityType 实体类型(小写),使用 '*' 表示匹配所有实体类型
* @param class-string<EntityParse> $parserClass Parser 类名
* @return void
* @throws InvalidArgumentException
*/
2025-12-15 15:47:56 +08:00
public static function register(int $platformId, string $entityType, string $parserClass): void
2025-11-27 13:40:58 +08:00
{
// 验证 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
);
}
// 注册映射
2025-12-15 15:47:56 +08:00
self::$registry[$platformId][$entityType] = $parserClass;
2025-11-27 13:40:58 +08:00
}
/**
* 批量注册平台映射
*
2025-12-15 15:47:56 +08:00
* @param array<int, array<string, class-string<EntityParse>>> $mappings
2025-11-27 13:40:58 +08:00
* @return void
*/
public static function registerBatch(array $mappings): void
{
2025-12-15 15:47:56 +08:00
foreach ($mappings as $platformId => $entities) {
2025-11-27 13:40:58 +08:00
foreach ($entities as $entityType => $parserClass) {
2025-12-15 15:47:56 +08:00
self::register($platformId, $entityType, $parserClass);
2025-11-27 13:40:58 +08:00
}
}
}
/**
* 获取所有已注册的映射
*
2025-12-15 15:47:56 +08:00
* @return array<int, array<string, class-string<EntityParse>>>
2025-11-27 13:40:58 +08:00
*/
public static function getRegistry(): array
{
return self::$registry;
}
/**
* 清空所有注册的映射(主要用于测试)
*
* @return void
*/
public static function clearRegistry(): void
{
self::$registry = [];
}
/**
* 检查指定平台和实体类型的 Parser 是否已注册
*
2025-12-15 15:47:56 +08:00
* @param int $platformId
2025-11-27 13:40:58 +08:00
* @param string $entityType
* @return bool
*/
2025-12-15 15:47:56 +08:00
public static function hasParser(int $platformId, string $entityType): bool
2025-11-27 13:40:58 +08:00
{
2025-12-15 15:47:56 +08:00
return isset(self::$registry[$platformId][$entityType])
|| isset(self::$registry[$platformId]['*']);
2025-11-27 13:40:58 +08:00
}
}