Commit 69f87043 authored by 刘小敏's avatar 刘小敏

AI生成图片和视频做成异步

parent a61eaaf8
......@@ -4,15 +4,8 @@ namespace addons\shopro\controller\user;
use addons\shopro\controller\Common;
use app\admin\model\shopro\AiTryon as AiTryOnModel;
use app\admin\model\shopro\user\User as UserModel;
use app\common\library\Upload;
use think\Db;
use think\helper\Time;
use addons\shopro\service\goods\GoodsService;
use addons\shopro\service\user\User;
use addons\shopro\service\user\Ai;
use think\Log;
class AiTryon extends Common
{
......@@ -26,32 +19,6 @@ class AiTryon extends Common
}
/**
* 保存至我的试戴
* 已废弃
*/
public function saveMyTryon()
{
// if (!$this->request->isPost()) {
// $this->error('');
// }
// $user = auth_user();
//
// $params = $this->request->only([
// 'url', 'goods_id', 'goods_title', 'goods_sku_id', 'goods_sku_name', 'goods_sku_image'
// ]);
// $params['user_id'] = $user->id;
//
// $this->svalidate($params, ".add");
//
// Db::transaction(function () use ($params) {
// $userAddress = new AiTryOnModel();
// $userAddress->save($params);
// });
//
// $this->success('保存成功' );
}
/**
* 我的试戴
*/
public function myTryon()
......@@ -131,7 +98,6 @@ class AiTryon extends Common
if ($res === 0) {
custom_log('[AI生成图片接口] 免费次数已用完 user_id:'.$user->id.' goods_id:'.$params['goods_id'], 'gen_ai');
// 免费试戴次数用完后,扣积分,试戴一次扣10积分
$userService = new User();
$res = $userService->deductPoints($user->id, 10);
if ($res === 0) {
custom_log('[AI生成图片接口] 积分不足 user_id:'.$user->id.' goods_id:'.$params['goods_id'], 'gen_ai');
......@@ -139,10 +105,6 @@ class AiTryon extends Common
}
}
// 合成AI图
$ai = new Ai();
$res = $ai->generateImage($params, $user->id, 'url');
// 我的试戴
$tryonModel = new AiTryOnModel();
$tryonData = [
......@@ -154,45 +116,18 @@ class AiTryon extends Common
'person_image' => $params['person_image'],
'ai_task_id' => '',
'ai_type' => 0,
'ai_status' => 0,
'ai_url' => '',
];
$id = $tryonModel->insertGetId($tryonData);
if ($res['code'] === 0) {
$tryonData['ai_status'] = 2;
$tryonData['ai_err_msg'] = $res['msg'];
$tryonModel->save($tryonData);
$this->error($res['msg']);
} else {
// 保存图片到本地
$aiService = new Ai();
$attachment = $aiService->saveFileLoc(isset($res['data']) ? $res['data'] : '', '.png', 'gen_ai');
// $upload = new Upload();
// $attachment = $upload->uploadBase64($res['data']);
// 记录入参日志
custom_log('[AI生成图片接口] user_id:'. $user->id .' 保存到本地接口接口出参:'.json_encode($attachment), 'gen_ai');
if($attachment === 0){
$tryonData['ai_status'] = 2;
$tryonData['ai_err_msg'] = '图片上传失败';
$tryonModel->save($tryonData);
$this->error('图片上传失败');
}
$tryonData['ai_status'] = 1;
$tryonData['ai_url'] = config('shopro.api_host') . $attachment;
$id = $tryonModel->insertGetId($tryonData);
// 试戴次数+1
$userService->addTryTimes($user->id);
// 试戴次数+1
$userService = new User();
$userService->addTryTimes($user->id);
// 推送队列
\think\Queue::push('\addons\shopro\job\AiImgGen@exec', ['user' => $user->toArray(), 'id' => $id ], 'shopro-high');
// 发送消息通知
$aiService = new Ai();
$aiService->sendNotification($user->id, $id);
$this->success('图片生成成功', config('shopro.api_host') . $attachment);
}
$this->success('图片生成中', $id);
}
......@@ -254,7 +189,6 @@ class AiTryon extends Common
if ($res === 0) {
custom_log('[AI生成视频接口] 免费次数已用完 user_id:'.$user->id.' goods_id:'.$params['goods_id'], 'gen_ai');
// 免费试戴次数用完后,扣积分,试戴一次扣10积分
$userService = new User();
$res = $userService->deductPoints($user->id, 10);
if ($res === 0) {
custom_log('[AI生成视频接口] 积分不足 user_id:'.$user->id.' goods_id:'.$params['goods_id'], 'gen_ai');
......@@ -262,11 +196,7 @@ class AiTryon extends Common
}
}
// 合成AI视频
$ai = new Ai();
$res = $ai->generateVideo($params, $user->id);
// 我的试戴
// 保存一条 我的试戴
$tryonModel = new AiTryOnModel();
$tryonData = [
'user_id' => $user->id,
......@@ -276,33 +206,19 @@ class AiTryon extends Common
'goods_image' => $params['jewelry_image'],
'person_image' => $params['person_image'],
'ai_type' => 1,
'ai_status' => 0,
'ai_task_id' => '',
];
$id = $tryonModel->insertGetId($tryonData);
if ($res['code'] === 0) {
$tryonData['ai_status'] = 2;
$tryonData['ai_err_msg'] = $res['msg'];
$tryonModel->insertGetId($tryonData);
// 试戴次数+1
$userService->addTryTimes($user->id);
$this->error($res['msg']);
// 推送队列
\think\Queue::push('\addons\shopro\job\AiVideoGen@save', ['user' => $user->toArray(), 'id' => $id], 'shopro-high');
} else {
// 保存一条 我的试戴
$tryonData['ai_status'] = 0;
$tryonData['ai_task_id'] = isset($res['data']) ? $res['data'] : '';
$id = $tryonModel->insertGetId($tryonData);
//试戴次数+1
$userService = new User();
$userService->addTryTimes($user->id);
$this->success('视频生成中', $id);
// 推送队列
\think\Queue::push('\addons\shopro\job\AiVideoGen@save', ['taskid' => $res['data'], 'user' => $user, 'id' => $id], 'shopro-high');
$this->success($res['msg'], $id);
}
}
......
<?php
namespace addons\shopro\job;
use addons\shopro\service\user\Ai;
use app\admin\model\shopro\AiTryon as AiTryOnModel;
use think\queue\Job;
use think\facade\Notification as NotificationFacade;
/**
* AI生成图片
*/
class AiImgGen extends BaseJob
{
// 调试日志 && 自定义日志
private function debug_log($text=''){
debug_echo($text);
custom_log($text, 'gen_ai');
}
/**
* AI视频结果处理
*/
public function exec(Job $job, $data)
{
try {
$id = $data['id'];
$user = $data['user'];
$user_id = isset($user['id']) ? $user['id'] : 0;
$this->debug_log('***************************** '.date('Y-m-d H:i:s'). 'AI图片生成脚本开始 userid '.$user_id.' id:'.$id.' ***********************************'.PHP_EOL);
// 校验入参
if (empty($id) || empty($user_id)) {
$this->debug_log('[AI图片异步队列][删除job]入参有空值 tryon_id:'.$id .' user_id:'.$user_id);
// 删除队列job
$job->delete();
return false;
}
// 查询tryon报表
$tryOn = AiTryOnModel::where('id', $id)->find();
if (!$tryOn) {
$this->debug_log('[AI图片异步队列][删除job]用户ID:'. $user_id .' id:'. $id .' 试戴记录不存在');
$job->delete();
return false;
}
// 合成AI图
$aiService = new Ai();
$res = $aiService->generateImage($tryOn->toArray(), $user_id, 'url');
// 我的试戴
if ($res['code'] === 0) {
AiTryOnModel::where('id', $id)->where('user_id', $user_id)->update(['ai_status' => 2, 'ai_err_msg' => $res['msg']]);
$job->delete();
} else {
// 保存图片到本地
$attachment = $aiService->saveFileLoc(isset($res['data']) ? $res['data'] : '', '.png', 'gen_ai');
// 记录入参日志
$this->debug_log('[AI生成图片接口] user_id:' . $user_id . ' 保存到本地接口接口出参:' . json_encode($attachment));
if ($attachment === 0) {
AiTryOnModel::where('id', $id)->where('user_id', $user_id)->update(['ai_status' => 2, 'ai_err_msg' => '图片上传失败']);
$job->delete();
return false;
}
AiTryOnModel::where('id', $id)->where('user_id', $user_id)->update(['ai_status' => 1, 'ai_url' => config('shopro.api_host') . $attachment]);
// 发送消息通知
$aiService->sendNotification($user_id, $id);
$this->debug_log('[AI图片异步队列]删除Job');
$job->delete();
}
} catch (\Exception $e) {
AiTryOnModel::where('id', $id)->where('user_id', $user_id)->update(['ai_status' => 2, 'ai_err_msg' => '图片生成异常']);
$this->debug_log('[AI图片异步队列]异常: id:'.$id.' userId '. $user_id );
$this->debug_log('[AI图片异步队列]异常: sql:' .db()->getLastSql());
format_log_error_custom_name($e, '[AI图片异步队列]', '', 'gen_ai');
// !empty($video_url) && $this->deleteLocalVideo($video_url); // 删除保存到本地的视频,如果已经保存了的话
// 删除队列job
$this->debug_log('[AI图片异步队列]删除Job');
$job->delete();
}
}
}
\ No newline at end of file
This diff is collapsed.
<?php
namespace addons\shopro\job;
use addons\shopro\library\HttpClient as Http;
use addons\shopro\service\user\Ai;
use app\admin\model\shopro\AiTryon as AiTryOnModel;
use think\queue\Job;
use think\facade\Notification as NotificationFacade;
/**
* AI生成视频查询状态任务
*/
class AiVideoRetry extends BaseJob
{
// 火山引擎方舟配置
private $ark_host = 'https://ark.cn-beijing.volces.com';
// 调试日志 && 自定义日志
private function debug_log($text=''){
debug_echo($text);
custom_log($text, 'gen_ai');
}
// 最大重试次数
const MAX_RETRY = 300;
// 重试延迟时间(秒)- 放入队尾时的延迟
const RETRY_DELAY = 1;
/**
* AI视频结果处理
*/
public function exec(Job $job, $data)
{
try {
// 获取重试次数(首次执行时为0)
$retryCount = isset($data['retry_count']) ? (int)$data['retry_count'] : 0;
$taskid = $data['taskId'];
$id = $data['id'];
$user = $data['user'];
$user_id = isset($user['id']) ? $user['id'] : 0;
$this->debug_log('***************************** '.date('Y-m-d H:i:s'). '查询AI视频生成状态脚本开始 userid '.$user_id.' taskid:'.$taskid.' id:'.$id.' ***********************************'.PHP_EOL);
// 校验入参
if (empty($id) || empty($user_id) || empty($taskid)) {
$this->debug_log('[AI视频异步队列]入参有空值 tryon_id:'.$id .' user_id:'.$user_id.' taskid:'.$taskid);
// 删除队列job
$this->debug_log('[AI视频]删除Job');
$job->delete();
return false;
}
// 1. 根据任务ID, 请求AI接口,查询视频状态
$url = $this->ark_host . '/api/v3/contents/generations/tasks/' . $taskid;
$params = [
'headers' => [
'Authorization' => 'Bearer ' . config('shopro.ai_api_key'),
'Content-Type' => 'application/json'
],
'verify' => false, // 关闭 SSL 证书验证
'allow_redirects' => true
];
$this->debug_log('[AI视频异步队列]请求AI接口入参 user_id:'.$user_id.' AI接口url:'.$url.' params:'.json_encode($params));
// 向AI接口 发送请求
$client = new Http();
$response = $client->get($url, $params);
$content = $response->getBody()->getContents();
$content = trim($content);
$content = preg_replace('/[^\x20-\x7E]/', '', $content);
$result = json_decode($content, true);
$this->debug_log('[AI视频异步队列]请求AI接口出参 response:'.json_encode($result));
// 2. AI接口 返回值处理 接口失败场景
if (isset($result['error'])){
$message = $result['error']['message'] ?? '未知错误';
AiTryOnModel::where('user_id', $user_id)
->where('ai_task_id', $taskid)
->where('id', $id)
->update(['ai_status' => 2, 'ai_err_msg' => $message]);
// 删除队列job
$this->debug_log('[AI视频异步队列]删除Job');
$job->delete();
return false;
}
// 3. AI接口 返回值处理 接口成功场景
if (isset($result['status'])){
switch ($result['status']) {
case 'succeeded':
// 任务成功
// 保存远程视频到本地
$aiService = new Ai();
$video_url = $aiService->saveFileLoc(isset($result['content']['video_url']) ? $result['content']['video_url'] : '', '.mp4', 'gen_ai');
// $video_url = $this->saveVideoLoc(isset($result['content']['video_url']) ? $result['content']['video_url'] : '');
$this->debug_log('[AI视频异步队列]保存本地接口返回值 video_url:'.$video_url);
if ($video_url === 0){
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_status' => 2, 'ai_err_msg' => '视频保存到本地失败']);
$this->debug_log('[AI视频异步队列]保存本地视频失败 sql: '.db()->getLastSql());
}
// 更新我的试戴
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_status' => 1,'ai_url' => config('shopro.api_host').$video_url, 'ai_err_msg' => '视频生成成功']);
$this->debug_log('[AI视频异步队列]保存本地视频成功 sql: '.db()->getLastSql());
// 发送成功消息通知
$aiService->sendNotification($user_id, $id);
// 删除队列job
$this->debug_log('[AI视频异步队列]删除Job');
$job->delete();
break;
case 'cancelled':
case 'failed':
case 'expired':
// 任务已过期
$msg = [
'cancelled' => '任务已取消',
'failed' => '视频生成失败',
'expired' => '任务已过期'
][$result['status']];
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_status' => 2, 'ai_err_msg' => $msg]);
$this->debug_log('[AI视频异步队列]AI生成视频任务 过期: taskId: ' . $taskid . 'userId '. $user_id );
$this->debug_log('[AI视频异步队列]AI生成视频任务 过期: result:'.json_encode($result));
$this->debug_log('[AI视频异步队列]AI生成视频任务 过期: sql:' .db()->getLastSql());
// 删除队列job
$this->debug_log('[AI视频异步队列]删除Job');
$job->delete();
break;
// 排队中 / 运行中
case 'queued':
case 'running':
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_err_msg' => '视频生成中']);
// 放入队尾,继续等待
$this->releaseJob($job, $data, $retryCount);
break;
}
}
} catch (\Exception $e) {
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_status' => 2, 'ai_err_msg' => '视频生成异常']);
$this->debug_log('[AI视频异步队列]异常: taskId: ' . $taskid . 'userId '. $user_id );
$this->debug_log('[AI视频异步队列]异常: sql:' .db()->getLastSql());
// 队列执行失败
format_log_error_custom_name($e, '[AI视频异步队列]', '', 'gen_ai');
// !empty($video_url) && $this->deleteLocalVideo($video_url); // 删除保存到本地的视频,如果已经保存了的话
// 删除队列job
$this->debug_log('[AI视频异步队列]删除Job');
$job->delete();
}
}
// 重试逻辑
private function releaseJob(Job $job, &$data, $retryCount)
{
$taskid = $data['taskId'];
$id = $data['id'];
$user = $data['user'];
$user_id = isset($user['id']) ? $user['id'] : 0;
if ($retryCount < self::MAX_RETRY) {
// 重试次数未达到上限,放入队尾继续重试
$retryCount++;
$data['retry_count'] = $retryCount; // 存储到data中
// release(0) 立即放入队尾,release($delay) 延迟后放入队尾
$this->debug_log('[AI视频异步队列]放入队尾Job: taskId: ' . $taskid . 'userId '. $user_id . 'retryCount: ' . $retryCount. 'id: ' . $id);
$job->release(self::RETRY_DELAY);
} else {
// 重试次数已达上限,标记失败
AiTryOnModel::where('user_id', $user_id)->where('ai_task_id', $taskid)->update(['ai_status' => 2, 'ai_err_msg' => '视频生成超时']);
$this->debug_log('[AI视频异步队列]超时未得到结果: taskId: ' . $taskid . 'userId '. $user_id . 'retryCount: ' . $retryCount. 'id: ' . $id);
$this->debug_log('[AI视频异步队列]超时未得到结果: sql:' .db()->getLastSql());
// 删除队列job
$this->debug_log('[AI视频异步队列]删除Job');
$job->delete();
}
}
}
\ No newline at end of file
......@@ -122,7 +122,7 @@ class Ai
/**
* AI 生成图片
* @param string $param 入参
* @param array $param 入参
* @param string $userId 入参
* @param string $response_format 入参
* @return object|array
......@@ -141,7 +141,7 @@ class Ai
'json' => [
"model" => "doubao-seedream-5-0-260128",
"prompt" => $prompt,
"image" => [$param['person_image'], $param['jewelry_image']],
"image" => [$param['person_image'], $param['goods_image']],
"sequential_image_generation" => "disabled",
"response_format" => $response_format,
"size" => "2K",
......@@ -213,12 +213,22 @@ class Ai
* @param string $userId 入参
* @return object|array
*/
public function generateVideo($params = [] , $userId = 0)
public function generateVideo($id , $userId = 0)
{
try {
// 查询tryon报表
$tryOn = AiTryOnModel::where('id', $id)->find();
if (!$tryOn) {
custom_log('[AI视频异步队列]用户ID:'. $userId .' id:'. $id .' 试戴记录不存在', 'gen_ai');
return [
'code' => 0 ,
'msg' => '试戴记录不存在'
];
}
// 合成AI图片
$resGenImg = $this->generateImage($params, $userId, 'url');
$resGenImg = $this->generateImage($tryOn->toArray(), $userId, 'url');
if ($resGenImg['code'] == 0) {
return $resGenImg;
}
......@@ -251,7 +261,6 @@ class Ai
"resolution" => "480p", // 分辨率:降档提速(原1080p改为720p)枚举值: 480p 720p 1080p
"duration" => 5,
"draft" => true,
// "safety_identifier" => md5($userId)
],
'timeout' => 300,
'verify' => false, // 关闭 SSL 证书验证
......@@ -259,7 +268,7 @@ class Ai
];
// 记录入参日志
custom_log('用户ID:'. $userId .' AI生成视频接口入参:'.json_encode($params), 'gen_ai');
custom_log('[AI视频异步队列]用户ID:'. $userId .' AI生成视频接口入参:'.json_encode($params), 'gen_ai');
// AI接口 发送请求
$client = new Http();
......@@ -271,7 +280,7 @@ class Ai
$result = json_decode($content, true);
// 记录出参日志
custom_log('用户ID:'. $userId .' AI生成视频接口返回:'.json_encode($result), 'gen_ai');
custom_log('[AI视频异步队列]用户ID:'. $userId .' AI生成视频接口返回:'.json_encode($result), 'gen_ai');
return [
'code' => 1 ,
......@@ -280,7 +289,7 @@ class Ai
];
} catch (\Exception $e) {
format_log_error_custom_name($e, '[AI生成视频接口异常] 用户ID:'. $userId , '', 'gen_ai' );
format_log_error_custom_name($e, '[AI视频异步队列][生成异常] 用户ID:'. $userId , '', 'gen_ai' );
return [
'code' => 0 ,
'msg' => '视频生成失败'
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment