作者 李宇航

合并分支 'lyh-server' 到 'master'

Lyh server



查看合并请求 !2487
@@ -9,11 +9,14 @@ @@ -9,11 +9,14 @@
9 9
10 namespace App\Console\Commands\Ai; 10 namespace App\Console\Commands\Ai;
11 11
  12 +use App\Models\Ai\AiVideo;
12 use App\Models\Product\Keyword; 13 use App\Models\Product\Keyword;
13 use App\Models\Product\Product; 14 use App\Models\Product\Product;
  15 +use App\Models\Project\AiBlogTask as AiBlogTaskModel;
14 use App\Models\Project\AiVideoAutoLog; 16 use App\Models\Project\AiVideoAutoLog;
15 use App\Models\Project\DeployOptimize; 17 use App\Models\Project\DeployOptimize;
16 use App\Models\Project\Project; 18 use App\Models\Project\Project;
  19 +use App\Services\AiVideoService;
17 use App\Services\MidJourneyService; 20 use App\Services\MidJourneyService;
18 use App\Services\ProjectServer; 21 use App\Services\ProjectServer;
19 use Illuminate\Console\Command; 22 use Illuminate\Console\Command;
@@ -63,7 +66,7 @@ class AiVideoAutoPublish extends Command @@ -63,7 +66,7 @@ class AiVideoAutoPublish extends Command
63 $this->output('开始自动发布Video文章'); 66 $this->output('开始自动发布Video文章');
64 $projectModel = new Project(); 67 $projectModel = new Project();
65 $optimizeModel = new DeployOptimize(); 68 $optimizeModel = new DeployOptimize();
66 - $projectList = $projectModel->list(['is_ai_video'=>1,'project_type'=>0,'delete_status'=>0,'site_status'=>0,'extend_type'=>0],'id',['id']); 69 + $projectList = $projectModel->list(['is_ai_video'=>1,'id'=>1,'project_type'=>0,'delete_status'=>0,'site_status'=>0,'extend_type'=>0],'id',['id']);
67 foreach ($projectList as $item){ 70 foreach ($projectList as $item){
68 $this->output("项目{$item['id']}开始自动发布"); 71 $this->output("项目{$item['id']}开始自动发布");
69 //获取当前是否开启自动发布aiVideo 72 //获取当前是否开启自动发布aiVideo
@@ -147,11 +150,13 @@ class AiVideoAutoPublish extends Command @@ -147,11 +150,13 @@ class AiVideoAutoPublish extends Command
147 $aiVideoAutoLogModel = new AiVideoAutoLog(); 150 $aiVideoAutoLogModel = new AiVideoAutoLog();
148 while (true){ 151 while (true){
149 if($number > 5){ 152 if($number > 5){
  153 + echo date('Y-m-d H:i:s').':当前生成图片数量已达到最大限度。'.$number.PHP_EOL;
150 sleep(300); 154 sleep(300);
151 continue; 155 continue;
152 } 156 }
153 $item = $aiVideoAutoLogModel->read(['status'=>0]); 157 $item = $aiVideoAutoLogModel->read(['status'=>0]);
154 - if(empty($info)){ 158 + if($item === false){
  159 + echo date('Y-m-d H:i:s').':无需生成图片的数据。'.PHP_EOL;
155 sleep(60); 160 sleep(60);
156 continue; 161 continue;
157 } 162 }
@@ -172,6 +177,73 @@ class AiVideoAutoPublish extends Command @@ -172,6 +177,73 @@ class AiVideoAutoPublish extends Command
172 } 177 }
173 178
174 /** 179 /**
  180 + * @remark :状态为1的数据推送至生成视频任务表
  181 + * @name :auto_save_video_task
  182 + * @author :lyh
  183 + * @method :post
  184 + * @time :2025/8/4 9:39
  185 + */
  186 + public function auto_save_video_task(){
  187 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  188 + while (true){
  189 + //获取任务id
  190 + $task_id = $this->getAutoTaskId();
  191 + if(empty($task_id)){
  192 + sleep(300);
  193 + continue;
  194 + }
  195 + $info = $aiVideoAutoLogModel->read(['id'=>$task_id]);
  196 + if($info === false){
  197 + echo date('Y-m-d H:i:s').':当前数据不存在或已被删除'.$task_id.PHP_EOL;
  198 + continue;
  199 + }
  200 + try {
  201 + $aiVideoTaskModel = new AiVideoTask();
  202 + $aiVideoService = new AiVideoService($info['project_id']);
  203 + $projectModel = new DeployOptimize();
  204 + $video_setting = $projectModel->getValue(['project_id'=>$info['project_id']],'video_setting');
  205 + $storage = $aiVideoTaskModel->videoSetting()[$video_setting ?? 1];
  206 + $result = $aiVideoService->createTask($info['title'],$info['remark'],$info['images'],[],$storage);
  207 + if($result['status'] == 200){
  208 + $aiVideoTaskModel->addReturnId(['task_id'=>$result['data']['task_id'],'project_id'=>$info['project_id'],'storage'=>$storage]);
  209 + ProjectServer::useProject($info['project_id']);
  210 + $aiVideoModel = new AiVideo();
  211 + $aiVideoModel->addReturnId(['title'=>$info['title'],'task_id'=>$result['data']['task_id'],'description'=>$info['remark'],'project_id'=>$info['project_id'],'images'=>json_encode($info['images'],true),'anchor'=>json_encode([],true)]);
  212 + DB::disconnect('custom_mysql');
  213 + }
  214 + }catch (\Exception $e){
  215 + echo '错误信息:'.$e->getMessage().PHP_EOL;
  216 + continue;
  217 + }
  218 + }
  219 +
  220 +
  221 + }
  222 +
  223 + /**
  224 + * @remark :火锅自动发布任务id
  225 + * @name :getAutoTaskId
  226 + * @author :lyh
  227 + * @method :post
  228 + * @time :2025/8/4 9:44
  229 + */
  230 + public function getAutoTaskId()
  231 + {
  232 + $task_id = Redis::rpop('auto_ai_video_task');
  233 + if (empty($task_id)) {
  234 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  235 + $ids = $aiVideoAutoLogModel->formatQuery(['status'=>1])->pluck('id');
  236 + if(!empty($ids)){
  237 + foreach ($ids as $id) {
  238 + Redis::lpush('auto_ai_video_task', $id);
  239 + }
  240 + }
  241 + $task_id = Redis::rpop('auto_ai_video_task');
  242 + }
  243 + return $task_id;
  244 + }
  245 +
  246 + /**
175 * @remark : 247 * @remark :
176 * @name :getAiVideoParam 248 * @name :getAiVideoParam
177 * @author :lyh 249 * @author :lyh
@@ -24,6 +24,7 @@ use App\Models\Template\BTemplateMain; @@ -24,6 +24,7 @@ use App\Models\Template\BTemplateMain;
24 use App\Models\Template\TemplateTypeMain; 24 use App\Models\Template\TemplateTypeMain;
25 use App\Models\WebSetting\WebSetting; 25 use App\Models\WebSetting\WebSetting;
26 use App\Services\AiBlogService; 26 use App\Services\AiBlogService;
  27 +use App\Services\CosService;
27 use App\Services\Geo\GeoService; 28 use App\Services\Geo\GeoService;
28 use App\Services\MidJourneyService; 29 use App\Services\MidJourneyService;
29 use App\Services\ProjectServer; 30 use App\Services\ProjectServer;
@@ -47,10 +48,13 @@ class lyhDemo extends Command @@ -47,10 +48,13 @@ class lyhDemo extends Command
47 protected $description = '更新路由'; 48 protected $description = '更新路由';
48 49
49 public function handle(){ 50 public function handle(){
50 - $content = "{Introducing the cutting-edge product from Zhejiang Yuexiang Gas Technology Co., Ltd., the Bracket Gas Flowmeter. This innovative gas flowmeter is designed to provide accurate and reliable measurements for a wide range of industrial applications,Equipped with advanced technology and precision components, the Bracket Gas Flowmeter ensures exceptional performance and durability. It offers highly accurate gas flow readings, allowing users to closely monitor and control gas consumption in various processes. The meter's user-friendly interface displays real-time flow rates and totalized flow data, facilitating efficient and convenient data management,With its robust construction and corrosion-resistant materials, the Bracket Gas Flowmeter guarantees long-term reliability, even in harsh operating conditions. It is compatible with various gases such as natural gas, propane, and hydrogen, making it a versatile choice for multiple industries including oil and gas, chemical, and manufacturing,Furthermore, the Bracket Gas Flowmeter is engineered with ease of installation and maintenance in mind. Its compact and space-saving design allows for flexible mounting options, while its modular construction enables quick and hassle-free maintenance. Additionally, it meets international standards for accuracy and safety, ensuring compliance with industry regulations,Choose the Bracket Gas Flowmeter for precise and efficient gas flow measurement, backed by the expertise and commitment of Zhejiang Yuexiang Gas Technology Co., Ltd},{Shop Quality Brackets: Top China Products & Services for Your Needs},4K,高清 --no logo --ar 16:9";  
51 - $midJourneyService = new MidJourneyService();  
52 - $result = $midJourneyService->imagine($content);  
53 - dd($result); 51 +// $content = "{Introducing the cutting-edge product from Zhejiang Yuexiang Gas Technology Co., Ltd., the Bracket Gas Flowmeter. This innovative gas flowmeter is designed to provide accurate and reliable measurements for a wide range of industrial applications,Equipped with advanced technology and precision components, the Bracket Gas Flowmeter ensures exceptional performance and durability. It offers highly accurate gas flow readings, allowing users to closely monitor and control gas consumption in various processes. The meter's user-friendly interface displays real-time flow rates and totalized flow data, facilitating efficient and convenient data management,With its robust construction and corrosion-resistant materials, the Bracket Gas Flowmeter guarantees long-term reliability, even in harsh operating conditions. It is compatible with various gases such as natural gas, propane, and hydrogen, making it a versatile choice for multiple industries including oil and gas, chemical, and manufacturing,Furthermore, the Bracket Gas Flowmeter is engineered with ease of installation and maintenance in mind. Its compact and space-saving design allows for flexible mounting options, while its modular construction enables quick and hassle-free maintenance. Additionally, it meets international standards for accuracy and safety, ensuring compliance with industry regulations,Choose the Bracket Gas Flowmeter for precise and efficient gas flow measurement, backed by the expertise and commitment of Zhejiang Yuexiang Gas Technology Co., Ltd},{Shop Quality Brackets: Top China Products & Services for Your Needs},4K,高清 --no logo --ar 16:9";
  52 +// $midJourneyService = new MidJourneyService();
  53 +// $result = $midJourneyService->imagine($content);
  54 + $url = 'https://ecdn6-nc.globalso.com/upload/p/1/png/2025-08/688dcebc26a7a59911.png';
  55 + $cosService = new CosService();
  56 + $data = $cosService->cropAndUploadToCOS($url);
  57 + dd($data);
54 } 58 }
55 59
56 /** 60 /**
@@ -11,6 +11,9 @@ namespace App\Http\Controllers\Api; @@ -11,6 +11,9 @@ namespace App\Http\Controllers\Api;
11 11
12 use App\Enums\Common\Code; 12 use App\Enums\Common\Code;
13 use App\Models\Project\AiVideoAutoLog; 13 use App\Models\Project\AiVideoAutoLog;
  14 +use App\Services\CosService;
  15 +use Illuminate\Support\Facades\Log;
  16 +use Illuminate\Support\Facades\Redis;
14 17
15 class AiVideoController extends BaseController 18 class AiVideoController extends BaseController
16 { 19 {
@@ -22,10 +25,43 @@ class AiVideoController extends BaseController @@ -22,10 +25,43 @@ class AiVideoController extends BaseController
22 * @time :2025/8/2 11:19 25 * @time :2025/8/2 11:19
23 */ 26 */
24 public function ImageCallBack(){ 27 public function ImageCallBack(){
25 - $data = $this->param;  
26 - @file_put_contents(storage_path('logs/lyh_error.log'), var_export($data, true) . PHP_EOL, FILE_APPEND);  
27 -// $aiVideoAutoLogModel = new AiVideoAutoLog();  
28 -// $aiVideoAutoLogModel->edit(['result'=>$data],['trigger_id'=>$data['trigger_id']]); 28 + $count = Redis::decr('ai_video_image');
  29 + if ($count < 0) {
  30 + Redis::set('ai_video_image', 0);
  31 + }
  32 + $data = $this->param['attachments'] ?? [];
  33 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  34 + if(empty($data) || empty($data['url'])){
  35 + $aiVideoAutoLogModel->edit(['status'=>9,'result'=>json_encode($this->param,true)],['trigger_id'=>$this->param['id']]);
  36 + }
  37 + //获取当前数据详情
  38 + $info = $aiVideoAutoLogModel->read(['trigger_id'=>$this->param['id']]);
  39 + if($info === false){
  40 + Log::channel('ai_video')->info('当前数据不存在或已被删除'.$this->param['id']);
  41 + }
  42 + //上传图片 返回cdn链接
  43 + $cosService = new CosService();
  44 + $imagePath = $cosService->uploadRemote($info['project_id'],'video',$data['url']);
  45 + try {
  46 + if($imagePath){
  47 + $cos = config('filesystems.disks.cos');
  48 + $url = $cos['cdn1'].'/'.$imagePath;
  49 + //裁剪图片为4张
  50 + $images = [];
  51 + $cosService = new CosService();
  52 + $data = $cosService->cropAndUploadToCOS($url);
  53 + if(!empty($data)){
  54 + foreach ($data as $item){
  55 + $images[] = ['url'=>$item,'alt'=>''];
  56 + }
  57 + }
  58 + $images = array_merge($images,$info['images']);
  59 + Log::channel('ai_video')->info('图片:'.json_encode($images));
  60 + $aiVideoAutoLogModel->edit(['images'=>$images,'result'=>json_encode($this->param,true),'status'=>1],['id'=>$info['id']]);
  61 + }
  62 + }catch (\Exception $e){
  63 + Log::channel('ai_video')->info('上传图片失败'.$e->getMessage());
  64 + }
29 $this->response('success'); 65 $this->response('success');
30 } 66 }
31 } 67 }
@@ -130,7 +130,6 @@ class CosService @@ -130,7 +130,6 @@ class CosService
130 'secretKey' => $cos['credentials']['secretKey'], 130 'secretKey' => $cos['credentials']['secretKey'],
131 ], 131 ],
132 ]); 132 ]);
133 -  
134 if(empty($body_str)){ 133 if(empty($body_str)){
135 try { 134 try {
136 $body_str = curl_c($file_url,false); 135 $body_str = curl_c($file_url,false);
@@ -145,7 +144,6 @@ class CosService @@ -145,7 +144,6 @@ class CosService
145 if(!$body_str){ 144 if(!$body_str){
146 return ''; 145 return '';
147 } 146 }
148 -  
149 try { 147 try {
150 $cosClient->putObject([ 148 $cosClient->putObject([
151 'Bucket' => $cos['bucket'], 149 'Bucket' => $cos['bucket'],
@@ -377,4 +375,77 @@ class CosService @@ -377,4 +375,77 @@ class CosService
377 } 375 }
378 return []; 376 return [];
379 } 377 }
  378 +
  379 + /**
  380 + * @remark :ai_video裁剪图片为4张
  381 + * @name :cropAndUploadToCOS
  382 + * @author :lyh
  383 + * @method :post
  384 + * @time :2025/8/2 16:52
  385 + */
  386 + public function cropAndUploadToCOS($imageUrl)
  387 + {
  388 + // 1. 下载远程图片内容
  389 + $imageData = file_get_contents($imageUrl);
  390 + if (!$imageData) {
  391 + return false;
  392 + }
  393 + // 2. 保存原图到临时文件
  394 + $tempOriginal = tempnam(sys_get_temp_dir(), 'original_') . '.png';
  395 + file_put_contents($tempOriginal, $imageData);
  396 + // 3. 使用 GD 加载图像
  397 + $src = imagecreatefrompng($tempOriginal);
  398 + if (!$src) {
  399 + return false;
  400 + }
  401 + $width = imagesx($src);
  402 + $height = imagesy($src);
  403 + $halfWidth = intval($width / 2);
  404 + $halfHeight = intval($height / 2);
  405 + // 4. 从原图 URL 提取路径信息
  406 + $parsed = parse_url($imageUrl);
  407 + $pathInfo = pathinfo($parsed['path']); // upload/p/1/png/2025-08/688dcebc26a7a59911.png
  408 + $cosPath = ltrim($pathInfo['dirname'], '/'); // 相对路径:upload/p/1/png/2025-08
  409 + $baseName = $pathInfo['filename']; // 文件名:688dcebc26a7a59911
  410 + $ext = $pathInfo['extension'] ?? 'png'; // 扩展名
  411 + // 5. 初始化 COS 客户端
  412 + $cos = config('filesystems.disks.cos');
  413 + $cosClient = new Client([
  414 + 'region' => $cos['region'],
  415 + 'credentials' => [
  416 + 'secretId' => $cos['credentials']['secretId'],
  417 + 'secretKey' => $cos['credentials']['secretKey'],
  418 + ],
  419 + ]);
  420 + // 6. 循环裁剪并上传
  421 + $resultPaths = [];
  422 + $index = 0;
  423 + $cos = config('filesystems.disks.cos');
  424 +
  425 + for ($y = 0; $y < 2; $y++) {
  426 + for ($x = 0; $x < 2; $x++) {
  427 + $crop = imagecreatetruecolor($halfWidth, $halfHeight);
  428 + imagecopy($crop, $src, 0, 0, $x * $halfWidth, $y * $halfHeight, $halfWidth, $halfHeight);
  429 + $tempCropped = tempnam(sys_get_temp_dir(), 'crop_') . '.png';
  430 + imagepng($crop, $tempCropped);
  431 + imagedestroy($crop);
  432 + // 新文件名,保持路径不变
  433 + $filename = $baseName . '_part' . $index++ . '.' . $ext;
  434 + $objectKey = $cosPath . '/' . $filename;
  435 + // 上传到 COS
  436 + $cosClient->putObject([
  437 + 'Bucket' => $cos['bucket'],
  438 + 'Key' => $objectKey,
  439 + 'Body' => fopen($tempCropped, 'rb'),
  440 + ]);
  441 + // 返回相对路径
  442 + $resultPaths[] = $cos['cdn1'].'/'.$objectKey;
  443 + unlink($tempCropped);
  444 + }
  445 + }
  446 + // 清理资源
  447 + imagedestroy($src);
  448 + unlink($tempOriginal);
  449 + return $resultPaths; // 相对路径数组
  450 + }
380 } 451 }