feat: add metadata workbench and shared seo rules
This commit is contained in:
@@ -255,6 +255,7 @@ class DomainModel extends BaseModel
|
||||
$arrTkdSeoCfg['provider'] = self::normalizeTkdProvider((string)($arrTkdSeoCfg['provider'] ?? self::TKD_PROVIDER_LOCAL));
|
||||
$arrSeoCfg['tkd'] = $arrTkdSeoCfg;
|
||||
|
||||
$arrRawCopyGenerationSeoCfg = is_array($mSeoCfg['copy_generation'] ?? null) ? $mSeoCfg['copy_generation'] : [];
|
||||
$arrCopyGenerationSeoCfg = is_array($arrSeoCfg['copy_generation'] ?? null) ? $arrSeoCfg['copy_generation'] : [];
|
||||
$arrCopyGenerationSeoCfg = array_merge(self::defaultSeoCfg()['copy_generation'], $arrCopyGenerationSeoCfg);
|
||||
$arrCopyGenerationSeoCfg['status'] = trim((string)($arrCopyGenerationSeoCfg['status'] ?? 'draft')) ?: 'draft';
|
||||
@@ -262,6 +263,38 @@ class DomainModel extends BaseModel
|
||||
$arrCopyGenerationSeoCfg['wait_for_ai_before_publish'] = (bool)($arrCopyGenerationSeoCfg['wait_for_ai_before_publish'] ?? false);
|
||||
$arrCopyGenerationSeoCfg['provider_preference'] = trim((string)($arrCopyGenerationSeoCfg['provider_preference'] ?? 'local_first')) ?: 'local_first';
|
||||
$arrCopyGenerationSeoCfg['ai_provider'] = self::normalizeTkdProvider((string)($arrCopyGenerationSeoCfg['ai_provider'] ?? self::TKD_PROVIDER_OPENAI));
|
||||
|
||||
$boolOpenAiCopyPreferred = $arrTkdSeoCfg['provider'] === self::TKD_PROVIDER_OPENAI
|
||||
|| $arrTkdSeoCfg['mode'] === self::TKD_MODE_AI_OPTIMIZE;
|
||||
if ($boolOpenAiCopyPreferred) {
|
||||
$boolLegacyWaitForAiDefault = array_key_exists('wait_for_ai_before_publish', $arrRawCopyGenerationSeoCfg)
|
||||
&& empty($arrRawCopyGenerationSeoCfg['wait_for_ai_before_publish']);
|
||||
$boolLegacyProviderPreferenceDefault = array_key_exists('provider_preference', $arrRawCopyGenerationSeoCfg)
|
||||
&& trim((string)($arrRawCopyGenerationSeoCfg['provider_preference'] ?? '')) === 'local_first';
|
||||
$boolLegacyStatusDefault = array_key_exists('status', $arrRawCopyGenerationSeoCfg)
|
||||
&& trim((string)($arrRawCopyGenerationSeoCfg['status'] ?? '')) === 'draft';
|
||||
|
||||
if (!array_key_exists('wait_for_ai_before_publish', $arrRawCopyGenerationSeoCfg) || $boolLegacyWaitForAiDefault) {
|
||||
$arrCopyGenerationSeoCfg['wait_for_ai_before_publish'] = true;
|
||||
}
|
||||
|
||||
if (!array_key_exists('provider_preference', $arrRawCopyGenerationSeoCfg)
|
||||
|| trim((string)($arrRawCopyGenerationSeoCfg['provider_preference'] ?? '')) === ''
|
||||
|| $boolLegacyProviderPreferenceDefault) {
|
||||
$arrCopyGenerationSeoCfg['provider_preference'] = 'ai_first';
|
||||
}
|
||||
|
||||
if (!array_key_exists('status', $arrRawCopyGenerationSeoCfg)
|
||||
|| trim((string)($arrRawCopyGenerationSeoCfg['status'] ?? '')) === ''
|
||||
|| $boolLegacyStatusDefault) {
|
||||
$arrCopyGenerationSeoCfg['status'] = 'ai_pending';
|
||||
}
|
||||
|
||||
if (!array_key_exists('ai_provider', $arrRawCopyGenerationSeoCfg) || trim((string)($arrRawCopyGenerationSeoCfg['ai_provider'] ?? '')) === '') {
|
||||
$arrCopyGenerationSeoCfg['ai_provider'] = self::TKD_PROVIDER_OPENAI;
|
||||
}
|
||||
}
|
||||
|
||||
$arrSeoCfg['copy_generation'] = $arrCopyGenerationSeoCfg;
|
||||
|
||||
$arrRuntimeAcceptance = is_array($arrSeoCfg['runtime_acceptance'] ?? null)
|
||||
|
||||
@@ -13,6 +13,19 @@ use Exception;
|
||||
*/
|
||||
class VideoModel extends MongoModel
|
||||
{
|
||||
protected array $fillableMetadataFields = [
|
||||
'v_actor',
|
||||
'v_director',
|
||||
'v_lang',
|
||||
'v_lang_en',
|
||||
'v_area',
|
||||
'v_area_en',
|
||||
'v_year',
|
||||
'v_remarks',
|
||||
'v_description',
|
||||
'v_publish_date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Undocumented variable
|
||||
*
|
||||
@@ -203,6 +216,506 @@ class VideoModel extends MongoModel
|
||||
return $this->findOne(['v_name_en' => $strNameEn]);
|
||||
}
|
||||
|
||||
public function buildEmptyMetadataBackfillSet(array $arrExistingVideo, array $arrIncomingVideo, string $strSource = 'crawler_backfill'): array
|
||||
{
|
||||
$arrUpdate = [];
|
||||
$arrFilledFields = [];
|
||||
|
||||
foreach ($this->fillableMetadataFields as $strField) {
|
||||
$mixedExisting = $arrExistingVideo[$strField] ?? null;
|
||||
$mixedIncoming = $arrIncomingVideo[$strField] ?? null;
|
||||
|
||||
if (!$this->isFieldMetadataMissing($strField, $mixedExisting)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->isFieldMetadataMissing($strField, $mixedIncoming)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mixedNormalized = $this->normalizeMetadataValueByField($strField, $mixedIncoming);
|
||||
if ($this->isFieldMetadataMissing($strField, $mixedNormalized)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrUpdate[$strField] = $mixedNormalized;
|
||||
$arrFilledFields[] = $strField;
|
||||
}
|
||||
|
||||
if (empty($arrFilledFields)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$arrUpdate['updated_at'] = new \MongoDB\BSON\UTCDateTime();
|
||||
$arrUpdate['v_metadata_fill'] = [
|
||||
'last_source' => $strSource,
|
||||
'updated_at' => new \MongoDB\BSON\UTCDateTime(),
|
||||
'filled_fields' => $arrFilledFields,
|
||||
];
|
||||
|
||||
return $arrUpdate;
|
||||
}
|
||||
|
||||
public function buildMissingMetadataAuditSummary(int $intSampleLimit = 20): array
|
||||
{
|
||||
$intSampleLimit = max(1, min($intSampleLimit, 100));
|
||||
$intTotal = (int)$this->getCol()->countDocuments();
|
||||
$arrFields = [];
|
||||
$arrAnyMissingFilters = [];
|
||||
|
||||
foreach ($this->fillableMetadataFields as $strField) {
|
||||
$arrFilter = $this->buildMissingMetadataFilter($strField);
|
||||
$intMissingCount = (int)$this->getCol()->countDocuments($arrFilter);
|
||||
$arrFields[] = [
|
||||
'field' => $strField,
|
||||
'missing_count' => $intMissingCount,
|
||||
'missing_ratio' => $intTotal > 0 ? round($intMissingCount / $intTotal, 4) : 0,
|
||||
];
|
||||
$arrAnyMissingFilters[] = $arrFilter;
|
||||
}
|
||||
|
||||
$arrAnyMissingFilter = empty($arrAnyMissingFilters) ? [] : ['$or' => $arrAnyMissingFilters];
|
||||
$intAnyMissingCount = empty($arrAnyMissingFilter) ? 0 : (int)$this->getCol()->countDocuments($arrAnyMissingFilter);
|
||||
|
||||
$Cursor = $this->getCol()->find($arrAnyMissingFilter, [
|
||||
'limit' => $intSampleLimit,
|
||||
'sort' => ['updated_at' => -1, 'v_id' => -1],
|
||||
'projection' => [
|
||||
'_id' => 0,
|
||||
'v_id' => 1,
|
||||
'v_name' => 1,
|
||||
'v_category' => 1,
|
||||
'v_year' => 1,
|
||||
'v_remarks' => 1,
|
||||
'updated_at' => 1,
|
||||
'v_actor' => 1,
|
||||
'v_director' => 1,
|
||||
'v_lang' => 1,
|
||||
'v_lang_en' => 1,
|
||||
'v_area' => 1,
|
||||
'v_area_en' => 1,
|
||||
'v_description' => 1,
|
||||
'v_publish_date' => 1,
|
||||
],
|
||||
'typeMap' => self::$arrOptions['typeMap'],
|
||||
]);
|
||||
|
||||
$arrSamples = [];
|
||||
foreach (iterator_to_array($Cursor) as $arrVideo) {
|
||||
$arrMissingFields = [];
|
||||
foreach ($this->fillableMetadataFields as $strField) {
|
||||
if ($this->isFieldMetadataMissing($strField, $arrVideo[$strField] ?? null)) {
|
||||
$arrMissingFields[] = $strField;
|
||||
}
|
||||
}
|
||||
|
||||
applyToKeys($arrVideo, ['updated_at'], 'formatMongoDate');
|
||||
|
||||
$arrSamples[] = [
|
||||
'v_id' => (int)($arrVideo['v_id'] ?? 0),
|
||||
'v_name' => (string)($arrVideo['v_name'] ?? ''),
|
||||
'v_category' => (string)($arrVideo['v_category'] ?? ''),
|
||||
'v_year' => is_array($arrVideo['v_year'] ?? null) ? '' : (string)($arrVideo['v_year'] ?? ''),
|
||||
'v_remarks' => is_array($arrVideo['v_remarks'] ?? null) ? '' : (string)($arrVideo['v_remarks'] ?? ''),
|
||||
'updated_at' => (string)($arrVideo['updated_at'] ?? ''),
|
||||
'missing_fields' => $arrMissingFields,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'generated_at' => date(DATE_ATOM),
|
||||
'total_videos' => $intTotal,
|
||||
'videos_with_any_missing_metadata' => $intAnyMissingCount,
|
||||
'fillable_fields' => $this->fillableMetadataFields,
|
||||
'field_stats' => $arrFields,
|
||||
'sample_limit' => $intSampleLimit,
|
||||
'samples' => $arrSamples,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildRecrawlPriorityQueueSummary(int $intLimit = 200, int $intScanLimit = 3000): array
|
||||
{
|
||||
$intLimit = max(1, min($intLimit, 5000));
|
||||
$intScanLimit = max($intLimit, min($intScanLimit, 20000));
|
||||
$arrCandidates = [];
|
||||
|
||||
$Cursor = $this->getCol()->find(
|
||||
[
|
||||
'$or' => [
|
||||
$this->buildMissingMetadataFilter('v_actor'),
|
||||
$this->buildMissingMetadataFilter('v_director'),
|
||||
],
|
||||
],
|
||||
[
|
||||
'projection' => [
|
||||
'_id' => 0,
|
||||
'v_id' => 1,
|
||||
'v_name' => 1,
|
||||
'v_category' => 1,
|
||||
'v_parent_category' => 1,
|
||||
'v_year' => 1,
|
||||
'v_actor' => 1,
|
||||
'v_director' => 1,
|
||||
'v_remarks' => 1,
|
||||
'v_isend' => 1,
|
||||
'v_publish_date' => 1,
|
||||
'v_play_url' => 1,
|
||||
'updated_at' => 1,
|
||||
],
|
||||
'limit' => $intScanLimit,
|
||||
'sort' => ['updated_at' => -1, 'v_id' => -1],
|
||||
'typeMap' => self::$arrOptions['typeMap'],
|
||||
]
|
||||
);
|
||||
|
||||
foreach ($Cursor as $arrVideo) {
|
||||
$intVId = (int)($arrVideo['v_id'] ?? 0);
|
||||
if ($intVId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrStats = VideoClicksModel::getInstance()->getStats($intVId);
|
||||
$arrPlayUrl = (array)($arrVideo['v_play_url'] ?? []);
|
||||
$arrSources = array_values(array_filter(array_map(static function ($mixedKey): string {
|
||||
return trim((string)$mixedKey);
|
||||
}, array_keys($arrPlayUrl)), static function (string $strKey): bool {
|
||||
return $strKey !== '';
|
||||
}));
|
||||
|
||||
$boolMissingActor = $this->isFieldMetadataMissing('v_actor', $arrVideo['v_actor'] ?? null);
|
||||
$boolMissingDirector = $this->isFieldMetadataMissing('v_director', $arrVideo['v_director'] ?? null);
|
||||
$strCategory = trim((string)($arrVideo['v_category'] ?? ''));
|
||||
$strParentCategory = trim((string)($arrVideo['v_parent_category'] ?? ''));
|
||||
$intPriorityScore = $this->calculateRecrawlPriorityScore(
|
||||
$arrVideo,
|
||||
$arrStats,
|
||||
$boolMissingActor,
|
||||
$boolMissingDirector,
|
||||
count($arrSources),
|
||||
$strCategory,
|
||||
$strParentCategory
|
||||
);
|
||||
|
||||
$arrCandidates[] = [
|
||||
'v_id' => $intVId,
|
||||
'v_name' => (string)($arrVideo['v_name'] ?? ''),
|
||||
'v_category' => $strCategory,
|
||||
'v_parent_category' => $strParentCategory,
|
||||
'v_year' => (string)($arrVideo['v_year'] ?? ''),
|
||||
'v_remarks' => (string)($arrVideo['v_remarks'] ?? ''),
|
||||
'v_isend' => (int)($arrVideo['v_isend'] ?? 0),
|
||||
'v_publish_date' => (string)($arrVideo['v_publish_date'] ?? ''),
|
||||
'missing_actor' => $boolMissingActor,
|
||||
'missing_director' => $boolMissingDirector,
|
||||
'missing_fields' => array_values(array_filter([
|
||||
$boolMissingActor ? 'v_actor' : '',
|
||||
$boolMissingDirector ? 'v_director' : '',
|
||||
])),
|
||||
'play_sources' => $arrSources,
|
||||
'play_source_count' => count($arrSources),
|
||||
'click_stats' => $arrStats,
|
||||
'priority_score' => $intPriorityScore,
|
||||
'updated_at' => ($arrVideo['updated_at'] ?? null) instanceof \MongoDB\BSON\UTCDateTime
|
||||
? formatMongoDate($arrVideo['updated_at'])
|
||||
: '',
|
||||
'priority_reason' => $this->buildRecrawlPriorityReason(
|
||||
$arrVideo,
|
||||
$boolMissingActor,
|
||||
$boolMissingDirector,
|
||||
$arrStats,
|
||||
(int)($arrVideo['v_isend'] ?? 0),
|
||||
count($arrSources),
|
||||
$strCategory,
|
||||
$strParentCategory
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
usort($arrCandidates, static function (array $arrA, array $arrB): int {
|
||||
$intScoreCompare = (int)($arrB['priority_score'] ?? 0) <=> (int)($arrA['priority_score'] ?? 0);
|
||||
if ($intScoreCompare !== 0) {
|
||||
return $intScoreCompare;
|
||||
}
|
||||
|
||||
$intWeeklyCompare = (int)(($arrB['click_stats'] ?? [])['weekly'] ?? 0) <=> (int)(($arrA['click_stats'] ?? [])['weekly'] ?? 0);
|
||||
if ($intWeeklyCompare !== 0) {
|
||||
return $intWeeklyCompare;
|
||||
}
|
||||
|
||||
$strPublishA = (string)($arrA['v_publish_date'] ?? '');
|
||||
$strPublishB = (string)($arrB['v_publish_date'] ?? '');
|
||||
$intPublishCompare = strcmp($strPublishB, $strPublishA);
|
||||
if ($intPublishCompare !== 0) {
|
||||
return $intPublishCompare;
|
||||
}
|
||||
|
||||
return (int)($arrB['v_id'] ?? 0) <=> (int)($arrA['v_id'] ?? 0);
|
||||
});
|
||||
|
||||
$arrCandidates = array_values(array_slice($arrCandidates, 0, $intLimit));
|
||||
|
||||
return [
|
||||
'generated_at' => date(DATE_ATOM),
|
||||
'limit' => $intLimit,
|
||||
'scan_limit' => $intScanLimit,
|
||||
'queue_size' => count($arrCandidates),
|
||||
'items' => $arrCandidates,
|
||||
];
|
||||
}
|
||||
|
||||
protected function isEmptyMetadataValue($mixedValue): bool
|
||||
{
|
||||
if ($mixedValue === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_string($mixedValue)) {
|
||||
return trim($mixedValue) === '';
|
||||
}
|
||||
|
||||
if (is_array($mixedValue)) {
|
||||
return count(array_filter($mixedValue, function ($mixedItem) {
|
||||
if (is_string($mixedItem)) {
|
||||
return trim($mixedItem) !== '';
|
||||
}
|
||||
|
||||
return !empty($mixedItem);
|
||||
})) === 0;
|
||||
}
|
||||
|
||||
return empty($mixedValue);
|
||||
}
|
||||
|
||||
public function isFieldMetadataMissing(string $strField, $mixedValue): bool
|
||||
{
|
||||
$mixedNormalized = $this->normalizeMetadataValueByField($strField, $mixedValue);
|
||||
return $this->isEmptyMetadataValue($mixedNormalized);
|
||||
}
|
||||
|
||||
protected function buildMissingMetadataFilter(string $strField): array
|
||||
{
|
||||
$arrInvalidValues = $this->getInvalidMetadataValuesByField($strField);
|
||||
|
||||
$arrOr = [
|
||||
[$strField => ['$exists' => false]],
|
||||
[$strField => null],
|
||||
[$strField => ''],
|
||||
[$strField => []],
|
||||
];
|
||||
|
||||
foreach ($arrInvalidValues as $strInvalid) {
|
||||
$arrOr[] = [$strField => $strInvalid];
|
||||
}
|
||||
|
||||
return [
|
||||
'$or' => $arrOr,
|
||||
];
|
||||
}
|
||||
|
||||
protected function buildRecrawlPriorityReason(
|
||||
array $arrVideo,
|
||||
bool $boolMissingActor,
|
||||
bool $boolMissingDirector,
|
||||
array $arrStats,
|
||||
int $intIsEnd,
|
||||
int $intSourceCount,
|
||||
string $strCategory,
|
||||
string $strParentCategory
|
||||
): string {
|
||||
$arrReasons = [];
|
||||
|
||||
if ($boolMissingActor) {
|
||||
$arrReasons[] = '演员缺失';
|
||||
}
|
||||
if ($boolMissingDirector) {
|
||||
$arrReasons[] = '导演缺失';
|
||||
}
|
||||
if ((int)($arrStats['weekly'] ?? 0) > 0 || (int)($arrStats['total'] ?? 0) > 0) {
|
||||
$arrReasons[] = '已有访问信号';
|
||||
}
|
||||
$intFreshDays = $this->resolveFreshDays((string)($arrVideo['v_publish_date'] ?? ''));
|
||||
if ($intFreshDays >= 0 && $intFreshDays <= 14) {
|
||||
$arrReasons[] = '近14天新内容';
|
||||
}
|
||||
if ($intIsEnd !== 1) {
|
||||
$arrReasons[] = '仍可能持续更新';
|
||||
}
|
||||
if ($intSourceCount >= 2) {
|
||||
$arrReasons[] = '已有多播放源可交叉重采';
|
||||
}
|
||||
$strCategoryKey = $strCategory . ' ' . $strParentCategory;
|
||||
if (preg_match('/综艺|动漫|连续剧|国产剧|韩剧|日剧/u', $strCategoryKey)) {
|
||||
$arrReasons[] = '高频更新题材';
|
||||
}
|
||||
|
||||
return implode(' / ', $arrReasons);
|
||||
}
|
||||
|
||||
protected function calculateRecrawlPriorityScore(
|
||||
array $arrVideo,
|
||||
array $arrStats,
|
||||
bool $boolMissingActor,
|
||||
bool $boolMissingDirector,
|
||||
int $intSourceCount,
|
||||
string $strCategory,
|
||||
string $strParentCategory
|
||||
): int {
|
||||
$intScore = 0;
|
||||
|
||||
if ($boolMissingActor) {
|
||||
$intScore += 30;
|
||||
}
|
||||
if ($boolMissingDirector) {
|
||||
$intScore += 35;
|
||||
}
|
||||
|
||||
$intFreshDays = $this->resolveFreshDays((string)($arrVideo['v_publish_date'] ?? ''));
|
||||
if ($intFreshDays >= 0 && $intFreshDays <= 3) {
|
||||
$intScore += 28;
|
||||
} elseif ($intFreshDays <= 7) {
|
||||
$intScore += 22;
|
||||
} elseif ($intFreshDays <= 14) {
|
||||
$intScore += 16;
|
||||
} elseif ($intFreshDays <= 30) {
|
||||
$intScore += 10;
|
||||
}
|
||||
|
||||
if ((int)($arrVideo['v_isend'] ?? 0) !== 1) {
|
||||
$intScore += 15;
|
||||
}
|
||||
|
||||
if ($intSourceCount >= 2) {
|
||||
$intScore += 10;
|
||||
} elseif ($intSourceCount === 1) {
|
||||
$intScore += 4;
|
||||
}
|
||||
|
||||
$strCategoryKey = $strCategory . ' ' . $strParentCategory;
|
||||
if (preg_match('/综艺/u', $strCategoryKey)) {
|
||||
$intScore += 10;
|
||||
} elseif (preg_match('/动漫/u', $strCategoryKey)) {
|
||||
$intScore += 9;
|
||||
} elseif (preg_match('/连续剧|国产剧|韩剧|日剧/u', $strCategoryKey)) {
|
||||
$intScore += 8;
|
||||
} elseif (preg_match('/短剧|现代都市|古装仙侠/u', $strCategoryKey)) {
|
||||
$intScore += 6;
|
||||
}
|
||||
|
||||
if (trim((string)($arrVideo['v_remarks'] ?? '')) !== '') {
|
||||
$intScore += 4;
|
||||
}
|
||||
|
||||
if (!empty($arrStats['total'])) {
|
||||
$intScore += min(18, (int)floor(log(max(1, (int)$arrStats['total']), 2)));
|
||||
}
|
||||
if (!empty($arrStats['weekly'])) {
|
||||
$intScore += min(12, (int)$arrStats['weekly']);
|
||||
}
|
||||
|
||||
return $intScore;
|
||||
}
|
||||
|
||||
protected function resolveFreshDays(string $strPublishDate): int
|
||||
{
|
||||
$strPublishDate = trim($strPublishDate);
|
||||
if ($strPublishDate === '') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$intTs = strtotime($strPublishDate);
|
||||
if ($intTs === false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
$intDiff = time() - $intTs;
|
||||
if ($intDiff < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int)floor($intDiff / 86400);
|
||||
}
|
||||
|
||||
protected function normalizeMetadataValue($mixedValue)
|
||||
{
|
||||
if (is_array($mixedValue)) {
|
||||
$arrValue = array_values(array_filter($mixedValue, function ($mixedItem) {
|
||||
if (is_string($mixedItem)) {
|
||||
return trim($mixedItem) !== '';
|
||||
}
|
||||
|
||||
return !empty($mixedItem);
|
||||
}));
|
||||
|
||||
return array_map(function ($mixedItem) {
|
||||
return is_string($mixedItem) ? trim($mixedItem) : $mixedItem;
|
||||
}, $arrValue);
|
||||
}
|
||||
|
||||
if (is_string($mixedValue)) {
|
||||
return trim($mixedValue);
|
||||
}
|
||||
|
||||
return $mixedValue;
|
||||
}
|
||||
|
||||
protected function normalizeMetadataValueByField(string $strField, $mixedValue)
|
||||
{
|
||||
$mixedNormalized = $this->normalizeMetadataValue($mixedValue);
|
||||
$arrInvalidValues = $this->getInvalidMetadataValuesByField($strField);
|
||||
|
||||
if (is_array($mixedNormalized)) {
|
||||
$arrValue = array_values(array_filter($mixedNormalized, function ($mixedItem) use ($arrInvalidValues) {
|
||||
if (!is_string($mixedItem)) {
|
||||
return !empty($mixedItem);
|
||||
}
|
||||
|
||||
$strValue = trim($mixedItem);
|
||||
if ($strValue === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !in_array($strValue, $arrInvalidValues, true);
|
||||
}));
|
||||
|
||||
return $arrValue;
|
||||
}
|
||||
|
||||
if (is_string($mixedNormalized)) {
|
||||
$strValue = trim($mixedNormalized);
|
||||
if (in_array($strValue, $arrInvalidValues, true)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $strValue;
|
||||
}
|
||||
|
||||
return $mixedNormalized;
|
||||
}
|
||||
|
||||
protected function getInvalidMetadataValuesByField(string $strField): array
|
||||
{
|
||||
$arrCommonInvalid = [
|
||||
'内详',
|
||||
'未知',
|
||||
'不详',
|
||||
'待补充',
|
||||
'暂无',
|
||||
'暂无信息',
|
||||
];
|
||||
|
||||
return match ($strField) {
|
||||
'v_actor' => array_values(array_unique(array_merge($arrCommonInvalid, [
|
||||
'未知演员',
|
||||
'演员未知',
|
||||
]))),
|
||||
'v_director' => array_values(array_unique(array_merge($arrCommonInvalid, [
|
||||
'未知导演',
|
||||
'导演未知',
|
||||
]))),
|
||||
default => $arrCommonInvalid,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询符合条件的随机多条小说
|
||||
|
||||
Reference in New Issue
Block a user