|
@@ -0,0 +1,408 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace App\Libs;
|
|
|
|
|
+
|
|
|
|
|
+use App\Models\MpUserPointsDetail;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 积分明细锚点解析
|
|
|
|
|
+ *
|
|
|
|
|
+ * 统一从任务/计费上下文推导「动漫 → 剧集 → 剧幕/分镜 → 剧本」锚点,用于:
|
|
|
|
|
+ * - 任务创建时写入 charge_info(AIVideoGenerationService / AIImageGenerationService),
|
|
|
|
|
+ * 只写入 ID 锚点:anime_id / episode_id / act_id(= alias_act_id)/ segment_id(= alias_segment_id)/ script_id,
|
|
|
|
|
+ * 剧集序号、剧幕序号、分镜序号等展示字段不入 charge_info;
|
|
|
|
|
+ * - 积分明细落库时写入 anime_id / anime_name / script_id / script_name(PointsService)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 上下文键名兼容两类写法:
|
|
|
|
|
+ * - 标准锚点:anime_id / episode_id / episode_number / act_id / segment_id / script_id / group_id
|
|
|
|
|
+ * - 历史参数:alias_act_id(mp_episode_segments.id)、alias_segment_id(mp_episode_segments.segment_id / 分镜号)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 规则:
|
|
|
|
|
+ * - 有分镜行ID或分镜号时,回查 mp_episode_segments 补全动漫、剧集、剧幕/分镜锚点;
|
|
|
|
|
+ * - 剧本优先取动漫绑定的剧本(mp_animes.script_id),其次取上下文显式传入的 script_id;
|
|
|
|
|
+ * - 推导不到的一律返回 0 / 空串,异常只记日志,绝不影响任务创建与计费主流程。
|
|
|
|
|
+ */
|
|
|
|
|
+class ScriptAnchor
|
|
|
|
|
+{
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分镜表读取字段(只取锚点链路需要的列)
|
|
|
|
|
+ */
|
|
|
|
|
+ const SEGMENT_COLUMNS = [
|
|
|
|
|
+ 'id',
|
|
|
|
|
+ 'anime_id',
|
|
|
|
|
+ 'episode_id',
|
|
|
|
|
+ 'episode_number',
|
|
|
|
|
+ 'segment_id',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 分镜行ID => 行数据 */
|
|
|
|
|
+ private static $segmentRowCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 分镜号 => 行数据 */
|
|
|
|
|
+ private static $segmentNoCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 动漫ID => 动漫行数据(anime_name、script_id) */
|
|
|
|
|
+ private static $animeRowCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 剧本分集分组ID => 剧本ID */
|
|
|
|
|
+ private static $groupScriptCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 剧本ID => 剧本名 */
|
|
|
|
|
+ private static $scriptNameCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 动漫ID-集数 => 分集ID */
|
|
|
|
|
+ private static $episodeIdCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 分集ID => 分集行数据 */
|
|
|
|
|
+ private static $episodeRowCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /** @var array 任务表锚点缓存 */
|
|
|
|
|
+ private static $taskAnchorCache = [];
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 空锚点(仅记录归属所需的 ID 锚点,编号等展示字段不入 charge_info)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function emptyAnchor(): array
|
|
|
|
|
+ {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'anime_id' => 0,
|
|
|
|
|
+ 'episode_id' => 0,
|
|
|
|
|
+ 'act_id' => 0,
|
|
|
|
|
+ 'segment_id' => '',
|
|
|
|
|
+ 'script_id' => 0,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 由上下文(任务创建参数或 charge_info)解析完整锚点
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $context
|
|
|
|
|
+ * @return array 见 emptyAnchor(),缺失项为 0 / 空串
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function resolve(array $context): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $anchor = self::emptyAnchor();
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 上下文显式传入的锚点
|
|
|
|
|
+ $animeId = (int)getProp($context, 'anime_id', 0);
|
|
|
|
|
+ $episodeId = (int)getProp($context, 'episode_id', 0);
|
|
|
|
|
+ $episodeNumber = (int)getProp($context, 'episode_number', 0);
|
|
|
|
|
+ $scriptId = (int)getProp($context, 'script_id', 0);
|
|
|
|
|
+
|
|
|
|
|
+ $actId = (int)getProp($context, 'act_id', 0);
|
|
|
|
|
+ if ($actId <= 0) {
|
|
|
|
|
+ $actId = (int)getProp($context, 'alias_act_id', 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $segmentId = self::normalizeSegmentNo(getProp($context, 'segment_id', ''));
|
|
|
|
|
+ if ($segmentId === '') {
|
|
|
|
|
+ $segmentId = self::normalizeSegmentNo(getProp($context, 'alias_segment_id', ''));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 由分镜行/分镜号补全动漫、剧集、剧幕/分镜
|
|
|
|
|
+ $segmentRow = [];
|
|
|
|
|
+ if ($actId > 0) {
|
|
|
|
|
+ $segmentRow = self::fromSegmentRowId($actId);
|
|
|
|
|
+ } elseif ($segmentId !== '') {
|
|
|
|
|
+ $segmentRow = self::fromSegmentNo($segmentId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!empty($segmentRow)) {
|
|
|
|
|
+ $animeId = $animeId ?: (int)$segmentRow['anime_id'];
|
|
|
|
|
+ $episodeId = $episodeId ?: (int)$segmentRow['episode_id'];
|
|
|
|
|
+ $episodeNumber = $episodeNumber ?: (int)$segmentRow['episode_number'];
|
|
|
|
|
+ $actId = (int)$segmentRow['id'];
|
|
|
|
|
+ if ($segmentId === '') {
|
|
|
|
|
+ $segmentId = (string)$segmentRow['segment_id'];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 只有动漫 + 集数时补全分集ID
|
|
|
|
|
+ if ($episodeId <= 0 && $animeId > 0 && $episodeNumber > 0) {
|
|
|
|
|
+ $episodeId = self::episodeIdByNumber($animeId, $episodeNumber);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 只有分集ID时补全动漫(用于取动漫绑定的剧本)
|
|
|
|
|
+ if ($episodeId > 0 && ($animeId <= 0 || $episodeNumber <= 0)) {
|
|
|
|
|
+ $episodeRow = self::fromEpisodeId($episodeId);
|
|
|
|
|
+ if ($episodeRow) {
|
|
|
|
|
+ $animeId = $animeId ?: (int)$episodeRow['anime_id'];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 剧本:动漫绑定优先,其次上下文脚本ID
|
|
|
|
|
+ $animeScriptId = $animeId > 0 ? self::scriptIdByAnime($animeId) : 0;
|
|
|
|
|
+ $scriptId = $animeScriptId > 0 ? $animeScriptId : $scriptId;
|
|
|
|
|
+
|
|
|
|
|
+ $anchor['anime_id'] = $animeId;
|
|
|
|
|
+ $anchor['episode_id'] = $episodeId;
|
|
|
|
|
+ $anchor['act_id'] = $actId;
|
|
|
|
|
+ $anchor['segment_id'] = $segmentId;
|
|
|
|
|
+ $anchor['script_id'] = $scriptId;
|
|
|
|
|
+
|
|
|
|
|
+ return $anchor;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ dLog('points')->warning('锚点解析失败(不影响主流程): ' . $e->getMessage());
|
|
|
|
|
+
|
|
|
|
|
+ return $anchor;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 将锚点合并进 charge_info
|
|
|
|
|
+ *
|
|
|
|
|
+ * - act_id / segment_id 统一按 alias_ 前缀记录:alias_act_id / alias_segment_id;
|
|
|
|
|
+ * - charge_info 中已有的值优先(尤其任务参数已带的 alias_act_id / alias_segment_id),缺失的才补充;
|
|
|
|
|
+ * - 未解析到的锚点不写入,避免冗余空值。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array $chargeInfo 已构建好的计费信息
|
|
|
|
|
+ * @param array $context 任务创建参数 / 计费上下文
|
|
|
|
|
+ * @return array 合并后的计费信息
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function mergeIntoChargeInfo(array $chargeInfo, array $context): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $anchor = self::resolve($context);
|
|
|
|
|
+
|
|
|
|
|
+ // act_id / segment_id 统一转换成 alias_ 前缀
|
|
|
|
|
+ $anchor['alias_act_id'] = $anchor['act_id'];
|
|
|
|
|
+ $anchor['alias_segment_id'] = $anchor['segment_id'];
|
|
|
|
|
+ unset($anchor['act_id'], $anchor['segment_id']);
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($anchor as $key => $value) {
|
|
|
|
|
+ if ($value === 0 || $value === '' || $value === null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 已有值优先(alias_ 开头的历史字段保留原值)
|
|
|
|
|
+ if (!empty(getProp($chargeInfo, $key, ''))) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $chargeInfo[$key] = $value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $chargeInfo;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分镜行ID(mp_episode_segments.id)→ 分镜行数据
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $rowId
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function fromSegmentRowId(int $rowId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($rowId <= 0) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($rowId, self::$segmentRowCache)) {
|
|
|
|
|
+ $row = DB::table('mp_episode_segments')->where('id', $rowId)->first(self::SEGMENT_COLUMNS);
|
|
|
|
|
+ self::$segmentRowCache[$rowId] = $row ? (array)$row : [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return self::$segmentRowCache[$rowId];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分镜号(mp_episode_segments.segment_id)→ 分镜行数据
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $segmentNo
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function fromSegmentNo(string $segmentNo): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $segmentNo = self::normalizeSegmentNo($segmentNo);
|
|
|
|
|
+ if ($segmentNo === '') {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($segmentNo, self::$segmentNoCache)) {
|
|
|
|
|
+ $row = DB::table('mp_episode_segments')->where('segment_id', $segmentNo)->first(self::SEGMENT_COLUMNS);
|
|
|
|
|
+ self::$segmentNoCache[$segmentNo] = $row ? (array)$row : [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return self::$segmentNoCache[$segmentNo];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 视频/图片任务ID → 锚点(回查任务表保存的关联片段,兼容未写锚点的历史任务)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $type 明细类型(video/image)
|
|
|
|
|
+ * @param int $taskId 任务表主键
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function anchorByTask(string $type, int $taskId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($taskId <= 0 || !in_array($type, [MpUserPointsDetail::TYPE_VIDEO, MpUserPointsDetail::TYPE_IMAGE], true)) {
|
|
|
|
|
+ return self::emptyAnchor();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $cacheKey = $type . ':' . $taskId;
|
|
|
|
|
+ if (!array_key_exists($cacheKey, self::$taskAnchorCache)) {
|
|
|
|
|
+ $table = $type === MpUserPointsDetail::TYPE_VIDEO ? 'mp_generate_video_tasks' : 'mp_generate_pic_tasks';
|
|
|
|
|
+ $task = DB::table($table)->where('id', $taskId)->first(['alias_act_id', 'alias_segment_id']);
|
|
|
|
|
+
|
|
|
|
|
+ $anchor = self::emptyAnchor();
|
|
|
|
|
+ if ($task) {
|
|
|
|
|
+ $context = [];
|
|
|
|
|
+ if ((int)getProp($task, 'alias_act_id', 0) > 0) {
|
|
|
|
|
+ $context['alias_act_id'] = (int)getProp($task, 'alias_act_id', 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ $segmentNo = self::normalizeSegmentNo(getProp($task, 'alias_segment_id', ''));
|
|
|
|
|
+ if ($segmentNo !== '') {
|
|
|
|
|
+ $context['alias_segment_id'] = $segmentNo;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($context) {
|
|
|
|
|
+ $anchor = self::resolve($context);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ self::$taskAnchorCache[$cacheKey] = $anchor;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return self::$taskAnchorCache[$cacheKey];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 动漫ID → 绑定剧本ID(未绑定返回 0)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $animeId
|
|
|
|
|
+ * @return int
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function scriptIdByAnime(int $animeId): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $animeRow = self::animeRowById($animeId);
|
|
|
|
|
+
|
|
|
|
|
+ return (int)($animeRow['script_id'] ?? 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 动漫ID → 动漫名(动漫不存在返回空串)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $animeId
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function animeNameById(int $animeId): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $animeRow = self::animeRowById($animeId);
|
|
|
|
|
+
|
|
|
|
|
+ return trim((string)($animeRow['anime_name'] ?? ''));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 动漫ID → 动漫行数据
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $animeId
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ private static function animeRowById(int $animeId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($animeId <= 0) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($animeId, self::$animeRowCache)) {
|
|
|
|
|
+ $row = DB::table('mp_animes')->where('id', $animeId)->first(['id', 'anime_name', 'script_id']);
|
|
|
|
|
+ self::$animeRowCache[$animeId] = $row ? (array)$row : [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return self::$animeRowCache[$animeId];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 剧本分集分组ID(mp_script_episode_group.id)→ 剧本ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $groupId
|
|
|
|
|
+ * @return int
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function scriptIdByGroup(int $groupId): int
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($groupId <= 0) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($groupId, self::$groupScriptCache)) {
|
|
|
|
|
+ self::$groupScriptCache[$groupId] = (int)DB::table('mp_script_episode_group')->where('id', $groupId)->value('script_id');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (int)self::$groupScriptCache[$groupId];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 剧本ID → 剧本名(剧本不存在返回空串)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $scriptId
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function scriptNameById(int $scriptId): string
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($scriptId <= 0) {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($scriptId, self::$scriptNameCache)) {
|
|
|
|
|
+ self::$scriptNameCache[$scriptId] = trim((string)DB::table('mp_scripts')->where('id', $scriptId)->value('script_name'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (string)self::$scriptNameCache[$scriptId];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 动漫ID + 集数 → 分集ID
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $animeId
|
|
|
|
|
+ * @param int $episodeNumber
|
|
|
|
|
+ * @return int
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function episodeIdByNumber(int $animeId, int $episodeNumber): int
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($animeId <= 0 || $episodeNumber <= 0) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $cacheKey = $animeId . '-' . $episodeNumber;
|
|
|
|
|
+ if (!array_key_exists($cacheKey, self::$episodeIdCache)) {
|
|
|
|
|
+ self::$episodeIdCache[$cacheKey] = (int)DB::table('mp_anime_episodes')
|
|
|
|
|
+ ->where('anime_id', $animeId)
|
|
|
|
|
+ ->where('episode_number', $episodeNumber)
|
|
|
|
|
+ ->value('id');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return (int)self::$episodeIdCache[$cacheKey];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 分集ID → 分集行数据(mp_anime_episodes)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $episodeId
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function fromEpisodeId(int $episodeId): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if ($episodeId <= 0) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!array_key_exists($episodeId, self::$episodeRowCache)) {
|
|
|
|
|
+ $row = DB::table('mp_anime_episodes')->where('id', $episodeId)->first(['id', 'anime_id', 'episode_number']);
|
|
|
|
|
+ self::$episodeRowCache[$episodeId] = $row ? (array)$row : [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return self::$episodeRowCache[$episodeId];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 归一化分镜号:空、0、字符串 "0" 均视为未传
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param mixed $segmentNo
|
|
|
|
|
+ * @return string
|
|
|
|
|
+ */
|
|
|
|
|
+ private static function normalizeSegmentNo($segmentNo): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $segmentNo = trim((string)$segmentNo);
|
|
|
|
|
+ if ($segmentNo === '' || $segmentNo === '0') {
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $segmentNo;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|