359 lines
11 KiB
PHP
359 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Entity\Parse;
|
|
|
|
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 类,工厂自动查找
|
|
* - 类名:{Platform}{Entity}Parser
|
|
* - 命名空间:App\Platform\{Platform}
|
|
* - 示例:App\Platform\Tmall\TmallOrderParser
|
|
*
|
|
* 查找顺序:
|
|
* 1. App\Platform\Tmall\TmallOrderParser
|
|
* 2. App\Platform\Tmall\OrderParser
|
|
* 3. App\Platform\TmallOrderParser
|
|
*
|
|
* 如果找不到对应的 Parser 类,将抛出 InvalidArgumentException
|
|
*
|
|
* 配置文件(可选):
|
|
* - 如需覆盖默认行为,可在 config/autoload/entity_parse.php 中配置
|
|
* - 支持通配符 '*' 匹配所有实体类型
|
|
*/
|
|
class EntityParseFactory
|
|
{
|
|
/**
|
|
* 平台与 Parser 类的映射表
|
|
* 格式:['platform_name' => ['entity_type' => ParserClass]]
|
|
*
|
|
* @var array<string, 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 中提取平台名称
|
|
$platformName = self::extractPlatformNameFromData($data);
|
|
|
|
// 3. 如果未指定实体类型,从 data 或 routing key 中提取
|
|
if ($entityType === null) {
|
|
$entityType = self::extractEntityTypeFromData($data, $message);
|
|
}
|
|
|
|
// 4. 获取对应的 Parser 类
|
|
$parserClass = self::resolveParserClass($platformName, $entityType);
|
|
|
|
// 5. 创建并返回 Parser 实例,传递解析后的数据
|
|
return $parserClass::create($data);
|
|
}
|
|
|
|
/**
|
|
* 从数据中提取平台名称(从 meta 字段中获取)
|
|
*
|
|
* @param array $data
|
|
* @return string
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
private static function extractPlatformNameFromData(array $data): string
|
|
{
|
|
if (!isset($data['meta']['platform_id'])) {
|
|
throw new InvalidArgumentException("Cannot extract platform name from data: meta.platform_id missing");
|
|
}
|
|
|
|
return self::resolvePlatformName($data['meta']['platform_id']);
|
|
}
|
|
|
|
/**
|
|
* 根据 platform_id 解析平台名称
|
|
*
|
|
* @param mixed $platformId
|
|
* @return string
|
|
*/
|
|
private static function resolvePlatformName($platformId): string
|
|
{
|
|
// TODO: 从配置或数据库中根据 platform_id 查找平台名称
|
|
// 临时方案:使用简单的映射
|
|
$platformMap = [
|
|
1 => 'taobao',
|
|
2 => 'tmall',
|
|
3 => 'jd',
|
|
// ... 其他平台
|
|
];
|
|
|
|
if (isset($platformMap[$platformId])) {
|
|
return $platformMap[$platformId];
|
|
}
|
|
|
|
throw new InvalidArgumentException("Unknown platform_id: {$platformId}");
|
|
}
|
|
|
|
/**
|
|
* 从数据或路由键中提取实体类型
|
|
*
|
|
* @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 string $platformName
|
|
* @param string $entityType
|
|
* @return class-string<EntityParse>
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
private static function resolveParserClass(string $platformName, string $entityType): string
|
|
{
|
|
// 1. 检查手动注册的映射
|
|
if (isset(self::$registry[$platformName][$entityType])) {
|
|
return self::$registry[$platformName][$entityType];
|
|
}
|
|
|
|
// 2. 检查通配符映射(支持 * 匹配所有实体类型)
|
|
if (isset(self::$registry[$platformName]['*'])) {
|
|
return self::$registry[$platformName]['*'];
|
|
}
|
|
|
|
// 3. 使用命名约定自动查找
|
|
$parserClass = self::findByConvention($platformName, $entityType);
|
|
|
|
if ($parserClass) {
|
|
// 自动注册以提高后续性能
|
|
self::register($platformName, $entityType, $parserClass);
|
|
return $parserClass;
|
|
}
|
|
|
|
throw new InvalidArgumentException(
|
|
"Cannot find parser for platform '{$platformName}' and entity '{$entityType}'. " .
|
|
"Please register it using EntityParseFactory::register() or create a parser class following the naming convention."
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 根据命名约定查找 Parser 类
|
|
*
|
|
* 命名约定:
|
|
* 1. 类名:{Platform}{Entity}Parser
|
|
* 2. 命名空间:App\Platform\{Platform}
|
|
*
|
|
* 示例:
|
|
* - Platform: tmall, Entity: order -> App\Platform\Tmall\TmallOrderParser
|
|
* - Platform: shopee, Entity: product -> App\Platform\Shopee\ShopeeProductParser
|
|
*
|
|
* @param string $platformName
|
|
* @param string $entityType
|
|
* @return class-string<EntityParse>|null
|
|
*/
|
|
private static function findByConvention(string $platformName, string $entityType): ?string
|
|
{
|
|
// 构建类名
|
|
$platformPascal = Str::of($platformName)->ucfirst()->toString();
|
|
$entityPascal = Str::of($entityType)->ucfirst()->toString();
|
|
|
|
// 尝试的类名列表
|
|
$possibleClasses = [
|
|
// App\Platform\Tmall\TmallOrderParser
|
|
"App\\Platform\\{$platformPascal}\\{$platformPascal}{$entityPascal}Parser",
|
|
// App\Platform\Tmall\OrderParser
|
|
"App\\Platform\\{$platformPascal}\\{$entityPascal}Parser",
|
|
// App\Platform\TmallOrderParser
|
|
"App\\Platform\\{$platformPascal}{$entityPascal}Parser",
|
|
];
|
|
|
|
foreach ($possibleClasses as $class) {
|
|
if (class_exists($class)) {
|
|
// 验证类是否继承自 EntityParse
|
|
if (is_subclass_of($class, EntityParse::class)) {
|
|
return $class;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* 手动注册平台与 Parser 的映射关系
|
|
*
|
|
* @param string $platformName 平台名称(小写)
|
|
* @param string $entityType 实体类型(小写),使用 '*' 表示匹配所有实体类型
|
|
* @param class-string<EntityParse> $parserClass Parser 类名
|
|
* @return void
|
|
* @throws InvalidArgumentException
|
|
*/
|
|
public static function register(string $platformName, 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[$platformName][$entityType] = $parserClass;
|
|
}
|
|
|
|
/**
|
|
* 批量注册平台映射
|
|
*
|
|
* @param array<string, array<string, class-string<EntityParse>>> $mappings
|
|
* @return void
|
|
*/
|
|
public static function registerBatch(array $mappings): void
|
|
{
|
|
foreach ($mappings as $platform => $entities) {
|
|
foreach ($entities as $entityType => $parserClass) {
|
|
self::register($platform, $entityType, $parserClass);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取所有已注册的映射
|
|
*
|
|
* @return array<string, array<string, class-string<EntityParse>>>
|
|
*/
|
|
public static function getRegistry(): array
|
|
{
|
|
return self::$registry;
|
|
}
|
|
|
|
/**
|
|
* 清空所有注册的映射(主要用于测试)
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function clearRegistry(): void
|
|
{
|
|
self::$registry = [];
|
|
}
|
|
|
|
/**
|
|
* 检查指定平台和实体类型的 Parser 是否已注册
|
|
*
|
|
* @param string $platformName
|
|
* @param string $entityType
|
|
* @return bool
|
|
*/
|
|
public static function hasParser(string $platformName, string $entityType): bool
|
|
{
|
|
return isset(self::$registry[$platformName][$entityType])
|
|
|| isset(self::$registry[$platformName]['*']);
|
|
}
|
|
}
|