Sfoglia il codice sorgente

1.通用生文、图和视频接口新增source_from字段用于记录使用来源
2.同步调整对应图片任务和视频任务的记录
3.同步调整积分明细表的数据记录

lh 4 settimane fa
parent
commit
03f9204a37

+ 34 - 0
app/Libs/ScriptAnchor.php

@@ -220,6 +220,40 @@ class ScriptAnchor
     }
     }
 
 
     /**
     /**
+     * 从请求参数提取计费锚点与业务来源标记(前端有传才记录)
+     *
+     * 支持字段:
+     * - 锚点:anime_id / episode_id / episode_number / script_id / alias_act_id / alias_segment_id
+     * - 来源标记:source_from(0 默认 / 1 资产 / 2 画布)→ 写入 charge_info.source_from,
+     *   (charge_info.source 已被 act/segment/generateText 等计费场景标记占用,因此前端字段统一用 source_from)
+     *
+     * @param array $data 请求参数
+     * @return array
+     */
+    public static function chargeContextFromRequest($data): array
+    {
+        $context = [];
+
+        foreach (['anime_id', 'episode_id', 'episode_number', 'script_id', 'alias_act_id'] as $key) {
+            $value = (int)getProp($data, $key, 0);
+            if ($value > 0) {
+                $context[$key] = $value;
+            }
+        }
+
+        // 分镜号是字符串(非纯数字),单独处理
+        $segmentNo = trim((string)getProp($data, 'alias_segment_id', ''));
+        if ($segmentNo !== '' && $segmentNo !== '0') {
+            $context['alias_segment_id'] = $segmentNo;
+        }
+
+        // 业务来源:0 默认 / 1 资产 / 2 画布(前端字段 source_from)
+        $context['source_from'] = (int)getProp($data, 'source_from', 0);
+
+        return $context;
+    }
+
+    /**
      * 分镜行ID(mp_episode_segments.id)→ 分镜行数据
      * 分镜行ID(mp_episode_segments.id)→ 分镜行数据
      *
      *
      * @param int $rowId
      * @param int $rowId

+ 4 - 1
app/Services/AIGeneration/AIImageGenerationService.php

@@ -104,7 +104,10 @@ class AIImageGenerationService
             'created_at' => date('Y-m-d H:i:s'),
             'created_at' => date('Y-m-d H:i:s'),
         ];
         ];
 
 
-        // 锚点数据(anime_id / episode_id / script_id,以及 alias_act_id / alias_segment_id),上下文没有的不记录
+        // 前端传入的锚点与业务来源标记(0 默认 / 1 资产 / 2 画布 → charge_info.source_from)
+        $chargeInfo = array_merge($chargeInfo, ScriptAnchor::chargeContextFromRequest($params));
+
+        // 锚点数据(anime_id / episode_id / episode_number / script_id,以及 alias_act_id / alias_segment_id),上下文没有的不记录
         return ScriptAnchor::mergeIntoChargeInfo($chargeInfo, $params);
         return ScriptAnchor::mergeIntoChargeInfo($chargeInfo, $params);
     }
     }
 
 

+ 4 - 1
app/Services/AIGeneration/AIVideoGenerationService.php

@@ -85,7 +85,10 @@ class AIVideoGenerationService
             'created_at' => date('Y-m-d H:i:s'),
             'created_at' => date('Y-m-d H:i:s'),
         ];
         ];
 
 
-        // 锚点数据(anime_id / episode_id / script_id,以及 alias_act_id / alias_segment_id),上下文没有的不记录
+        // 前端传入的锚点与业务来源标记(0 默认 / 1 资产 / 2 画布 → charge_info.source_from)
+        $chargeInfo = array_merge($chargeInfo, ScriptAnchor::chargeContextFromRequest($params));
+
+        // 锚点数据(anime_id / episode_id / episode_number / script_id,以及 alias_act_id / alias_segment_id),上下文没有的不记录
         return ScriptAnchor::mergeIntoChargeInfo($chargeInfo, $params);
         return ScriptAnchor::mergeIntoChargeInfo($chargeInfo, $params);
     }
     }
 
 

+ 37 - 1
app/Services/Anime/AnimeService.php

@@ -5547,6 +5547,32 @@ class AnimeService
      * @param array $data 请求参数
      * @param array $data 请求参数
      * @return array
      * @return array
      */
      */
+    /**
+     * 追加计费锚点与业务来源标记(前端有传才记录,供 charge_info 落库)
+     *
+     * 支持:anime_id / episode_id / episode_number / script_id / alias_act_id / alias_segment_id
+     * 以及 source_from(0 默认 / 1 资产 / 2 画布 → charge_info.source_from)
+     *
+     * @param array $params
+     * @param array $data
+     * @return array
+     */
+    private function appendChargeAnchors(array $params, array $data): array
+    {
+        foreach (['anime_id', 'episode_id', 'episode_number', 'script_id', 'alias_act_id', 'alias_segment_id'] as $key) {
+            $value = getProp($data, $key, '');
+            if ($value === '' || $value === null || $value === 0 || $value === '0') {
+                continue;
+            }
+            $params[$key] = $value;
+        }
+
+        // 业务来源:0 默认 / 1 资产 / 2 画布(前端字段 source_from)
+        $params['source_from'] = (int)getProp($data, 'source_from', 0);
+
+        return $params;
+    }
+
     public function generateVideo($data) {
     public function generateVideo($data) {
         $prompt = getProp($data, 'prompt');
         $prompt = getProp($data, 'prompt');
         if (empty($prompt)) {
         if (empty($prompt)) {
@@ -5642,6 +5668,9 @@ class AnimeService
             'camera_fixed' => getProp($data, 'camera_fixed', false),
             'camera_fixed' => getProp($data, 'camera_fixed', false),
         ];
         ];
 
 
+        // 计费锚点与业务来源(0 默认 / 1 资产 / 2 画布),前端有传才记录
+        $videoParams = $this->appendChargeAnchors($videoParams, $data);
+
         if ($first_frame_url) $videoParams['first_frame_url'] = $first_frame_url;
         if ($first_frame_url) $videoParams['first_frame_url'] = $first_frame_url;
         if ($tail_frame_url) $videoParams['tail_frame_url'] = $tail_frame_url;
         if ($tail_frame_url) $videoParams['tail_frame_url'] = $tail_frame_url;
 
 
@@ -5674,6 +5703,7 @@ class AnimeService
                 'video_ratio' => $ratio,
                 'video_ratio' => $ratio,
                 'seed' => $seed,
                 'seed' => $seed,
             ];
             ];
+            $unifiedParams = $this->appendChargeAnchors($unifiedParams, $data);
 
 
             // 构建parameters参数
             // 构建parameters参数
             $parameters = [
             $parameters = [
@@ -6562,9 +6592,15 @@ class AnimeService
                 'width' => $width,
                 'width' => $width,
                 'height' => $height,
                 'height' => $height,
                 'image_num' => $imageNum,
                 'image_num' => $imageNum,
-                // 计费锚点(动漫/剧集/剧本),上下文没有时不记录
+                // 计费锚点(动漫/剧集/剧本/分镜),上下文没有时不记录
                 'anime_id' => getProp($data, 'anime_id'),
                 'anime_id' => getProp($data, 'anime_id'),
                 'episode_id' => $episode_id,
                 'episode_id' => $episode_id,
+                'episode_number' => getProp($data, 'episode_number'),
+                'script_id' => getProp($data, 'script_id'),
+                'alias_act_id' => getProp($data, 'alias_act_id'),
+                'alias_segment_id' => getProp($data, 'alias_segment_id'),
+                // 业务来源:0 默认 / 1 资产 / 2 画布(前端字段 source_from)
+                'source_from' => (int)getProp($data, 'source_from', 0),
             ];
             ];
             if ($ref_img_urls) $params['ref_img_urls'] = is_array($ref_img_urls) ? $ref_img_urls : [$ref_img_urls];
             if ($ref_img_urls) $params['ref_img_urls'] = is_array($ref_img_urls) ? $ref_img_urls : [$ref_img_urls];
             
             

+ 21 - 15
app/Services/DeepSeek/DeepSeekService.php

@@ -8,6 +8,7 @@ use App\Facade\Site;
 use App\Libs\ActShowContent;
 use App\Libs\ActShowContent;
 use App\Libs\JsonSafeRepair;
 use App\Libs\JsonSafeRepair;
 use App\Libs\Utils;
 use App\Libs\Utils;
+use App\Libs\ScriptAnchor;
 use GuzzleHttp\Client;
 use GuzzleHttp\Client;
 use DateTime;
 use DateTime;
 use DateTimeZone;
 use DateTimeZone;
@@ -326,12 +327,15 @@ class DeepSeekService
         // 根据模型类型选择流式实现
         // 根据模型类型选择流式实现
         $streamGenerator = $this->dispatchStreamByModel($post_data);
         $streamGenerator = $this->dispatchStreamByModel($post_data);
 
 
+        // 计费锚点与业务来源(前端有传才记录)
+        $chargeContext = ScriptAnchor::chargeContextFromRequest($data);
+
         if ($chargePoints > 0) {
         if ($chargePoints > 0) {
-            return $this->wrapGenerateTextCharge($streamGenerator, $uid, $model, $chargePoints, $chargeModality);
+            return $this->wrapGenerateTextCharge($streamGenerator, $uid, $model, $chargePoints, $chargeModality, $chargeContext);
         }
         }
 
 
         // 免费场景(0 积分):流式生成成功后仅记录 token 使用明细
         // 免费场景(0 积分):流式生成成功后仅记录 token 使用明细
-        return $this->wrapGenerateTextTokenRecord($streamGenerator, $uid, $model);
+        return $this->wrapGenerateTextTokenRecord($streamGenerator, $uid, $model, $chargeContext);
     }
     }
 
 
     /**
     /**
@@ -342,15 +346,15 @@ class DeepSeekService
      * @param string     $model
      * @param string     $model
      * @return \Generator
      * @return \Generator
      */
      */
-    private function wrapGenerateTextTokenRecord(\Generator $generator, int $uid, string $model, string $source = 'generateText'): \Generator
+    private function wrapGenerateTextTokenRecord(\Generator $generator, int $uid, string $model, array $chargeContext = []): \Generator
     {
     {
         $recorded = false;
         $recorded = false;
         foreach ($generator as $chunk) {
         foreach ($generator as $chunk) {
             if (!$recorded && isset($chunk['type']) && $chunk['type'] === 'done') {
             if (!$recorded && isset($chunk['type']) && $chunk['type'] === 'done') {
-                $this->recordTextTokenOnly($uid, $this->pointsService->getTokensFromUsage(getProp($chunk, 'usage', [])), [
+                $this->recordTextTokenOnly($uid, $this->pointsService->getTokensFromUsage(getProp($chunk, 'usage', [])), array_merge([
                     'model' => $model,
                     'model' => $model,
-                    'source' => $source,
-                ], $model);
+                    'source' => 'generateText',
+                ], $chargeContext), $model);
                 $recorded = true;
                 $recorded = true;
             }
             }
             yield $chunk;
             yield $chunk;
@@ -367,14 +371,14 @@ class DeepSeekService
      * @param string     $modality
      * @param string     $modality
      * @return \Generator
      * @return \Generator
      */
      */
-    private function wrapGenerateTextCharge(\Generator $generator, int $uid, string $model, int $points, string $modality): \Generator
+    private function wrapGenerateTextCharge(\Generator $generator, int $uid, string $model, int $points, string $modality, array $chargeContext = []): \Generator
     {
     {
         $charged = false;
         $charged = false;
         foreach ($generator as $chunk) {
         foreach ($generator as $chunk) {
             if (!$charged && isset($chunk['type']) && $chunk['type'] === 'done') {
             if (!$charged && isset($chunk['type']) && $chunk['type'] === 'done') {
                 $fullContent = (string)getProp($chunk, 'full_content', getProp($chunk, 'answer', ''));
                 $fullContent = (string)getProp($chunk, 'full_content', getProp($chunk, 'answer', ''));
                 if ($fullContent !== '') {
                 if ($fullContent !== '') {
-                    $this->chargeMultimodal($uid, $model, $points, $modality, getProp($chunk, 'usage', []));
+                    $this->chargeMultimodal($uid, $model, $points, $modality, getProp($chunk, 'usage', []), $chargeContext);
                     $charged = true;
                     $charged = true;
                 }
                 }
             }
             }
@@ -392,17 +396,17 @@ class DeepSeekService
      * @param mixed  $usage
      * @param mixed  $usage
      * @return void
      * @return void
      */
      */
-    private function chargeMultimodal(int $uid, string $model, int $points, string $modality, $usage): void
+    private function chargeMultimodal(int $uid, string $model, int $points, string $modality, $usage, array $chargeContext = []): void
     {
     {
         $result = $this->pointsService->recordChatCharge(
         $result = $this->pointsService->recordChatCharge(
             $uid,
             $uid,
             $points,
             $points,
             $this->pointsService->getTokensFromUsage($usage),
             $this->pointsService->getTokensFromUsage($usage),
-            [
+            array_merge([
                 'model'    => $model,
                 'model'    => $model,
                 'modality' => $modality,
                 'modality' => $modality,
                 'source'   => 'generateText',
                 'source'   => 'generateText',
-            ],
+            ], $chargeContext),
             '',
             '',
             $model
             $model
         );
         );
@@ -989,6 +993,8 @@ class DeepSeekService
             'sequence' => $sequence,
             'sequence' => $sequence,
             'original_prompt' => getProp($data, 'prompt', ''),
             'original_prompt' => getProp($data, 'prompt', ''),
             'response_format' => $responseFormat,
             'response_format' => $responseFormat,
+            // 计费锚点与业务来源(前端有传才记录)
+            'charge_context' => ScriptAnchor::chargeContextFromRequest($data),
         ]);
         ]);
     }
     }
 
 
@@ -1088,12 +1094,12 @@ class DeepSeekService
                         ]);
                         ]);
                     }
                     }
                     if ($ctx['has_valid_script'] && $fullContent !== '') {
                     if ($ctx['has_valid_script'] && $fullContent !== '') {
-                        $this->chargeChatSuccess($ctx['uid'], $this->pointsService->getTokensFromUsage($usage), [
+                        $this->chargeChatSuccess($ctx['uid'], $this->pointsService->getTokensFromUsage($usage), array_merge([
                             'model' => $ctx['model'],
                             'model' => $ctx['model'],
                             'script_id' => $ctx['script_id'],
                             'script_id' => $ctx['script_id'],
                             'sequence' => $ctx['sequence'],
                             'sequence' => $ctx['sequence'],
                             'source' => 'newGenerateText',
                             'source' => 'newGenerateText',
-                        ]);
+                        ], $ctx['charge_context'] ?? []));
                     }
                     }
                 });
                 });
 
 
@@ -1729,12 +1735,12 @@ class DeepSeekService
 
 
         // 调用成功且存在有效剧本时,返回结果前扣除积分并记录明细(chat类型10积分)
         // 调用成功且存在有效剧本时,返回结果前扣除积分并记录明细(chat类型10积分)
         if ($hasValidScript && $aiResult && !empty($aiResult['content'])) {
         if ($hasValidScript && $aiResult && !empty($aiResult['content'])) {
-            $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($aiResult['usage'] ?? []), [
+            $this->chargeChatSuccess($uid, $this->pointsService->getTokensFromUsage($aiResult['usage'] ?? []), array_merge([
                 'model' => $model,
                 'model' => $model,
                 'script_id' => $script_id,
                 'script_id' => $script_id,
                 'sequence' => $sequence,
                 'sequence' => $sequence,
                 'source' => 'newGenerateText',
                 'source' => 'newGenerateText',
-            ]);
+            ], ScriptAnchor::chargeContextFromRequest($data)));
         }
         }
         
         
         // 新增: 如果提供了script_id,保存对话记录
         // 新增: 如果提供了script_id,保存对话记录