作者 刘锟

Merge remote-tracking branch 'origin/master' into akun

@@ -133,10 +133,8 @@ class AiBlogAutoPublish extends Command @@ -133,10 +133,8 @@ class AiBlogAutoPublish extends Command
133 $aiBlogTaskModel = new AiBlogTaskModel(); 133 $aiBlogTaskModel = new AiBlogTaskModel();
134 $next_auto_date = date('Y-m-d', strtotime('+' . mt_rand($frequency[0],$frequency[1]) . 'days')); //每3-6天自动发布 134 $next_auto_date = date('Y-m-d', strtotime('+' . mt_rand($frequency[0],$frequency[1]) . 'days')); //每3-6天自动发布
135 $aiBlogTaskModel->addReturnId(['project_id' => $project_id, 'type' => 2, 'task_id' => $result['data']['task_id'], 'status' => 1, 'next_auto_date' => $next_auto_date]); 135 $aiBlogTaskModel->addReturnId(['project_id' => $project_id, 'type' => 2, 'task_id' => $result['data']['task_id'], 'status' => 1, 'next_auto_date' => $next_auto_date]);
136 -  
137 ProjectServer::useProject($project_id); 136 ProjectServer::useProject($project_id);
138 $aiBlogModel = new AiBlog(); 137 $aiBlogModel = new AiBlog();
139 -  
140 $start = strtotime('10:00:00'); 138 $start = strtotime('10:00:00');
141 $end = strtotime('16:00:00'); 139 $end = strtotime('16:00:00');
142 $randomTimestamp = mt_rand($start, $end); 140 $randomTimestamp = mt_rand($start, $end);
@@ -9,11 +9,15 @@ @@ -9,11 +9,15 @@
9 9
10 namespace App\Console\Commands\Ai; 10 namespace App\Console\Commands\Ai;
11 11
  12 +use App\Models\Ai\AiVideo;
  13 +use App\Models\Domain\DomainInfo;
12 use App\Models\Product\Keyword; 14 use App\Models\Product\Keyword;
13 use App\Models\Product\Product; 15 use App\Models\Product\Product;
  16 +use App\Models\Project\AiBlogTask as AiBlogTaskModel;
14 use App\Models\Project\AiVideoAutoLog; 17 use App\Models\Project\AiVideoAutoLog;
15 use App\Models\Project\DeployOptimize; 18 use App\Models\Project\DeployOptimize;
16 use App\Models\Project\Project; 19 use App\Models\Project\Project;
  20 +use App\Services\AiVideoService;
17 use App\Services\MidJourneyService; 21 use App\Services\MidJourneyService;
18 use App\Services\ProjectServer; 22 use App\Services\ProjectServer;
19 use Illuminate\Console\Command; 23 use Illuminate\Console\Command;
@@ -50,6 +54,12 @@ class AiVideoAutoPublish extends Command @@ -50,6 +54,12 @@ class AiVideoAutoPublish extends Command
50 if($action == 'auto_publish'){ 54 if($action == 'auto_publish'){
51 $this->auto_publish(); 55 $this->auto_publish();
52 } 56 }
  57 + if($action == 'auto_send_video'){
  58 + $this->auto_send_video();
  59 + }
  60 + if($action == 'auto_save_video_task'){
  61 + $this->auto_save_video_task();
  62 + }
53 } 63 }
54 64
55 /** 65 /**
@@ -63,7 +73,7 @@ class AiVideoAutoPublish extends Command @@ -63,7 +73,7 @@ class AiVideoAutoPublish extends Command
63 $this->output('开始自动发布Video文章'); 73 $this->output('开始自动发布Video文章');
64 $projectModel = new Project(); 74 $projectModel = new Project();
65 $optimizeModel = new DeployOptimize(); 75 $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']); 76 + $projectList = $projectModel->list(['is_ai_video'=>1,'id'=>1,'delete_status'=>0,'site_status'=>0,'extend_type'=>0],'id',['id','project_type']);
67 foreach ($projectList as $item){ 77 foreach ($projectList as $item){
68 $this->output("项目{$item['id']}开始自动发布"); 78 $this->output("项目{$item['id']}开始自动发布");
69 //获取当前是否开启自动发布aiVideo 79 //获取当前是否开启自动发布aiVideo
@@ -82,9 +92,15 @@ class AiVideoAutoPublish extends Command @@ -82,9 +92,15 @@ class AiVideoAutoPublish extends Command
82 $this->output("项目{$item['id']}未到执行时间" . $next_auto_date); 92 $this->output("项目{$item['id']}未到执行时间" . $next_auto_date);
83 continue; 93 continue;
84 } 94 }
85 - //获取当前网站的标题  
86 - ProjectServer::useProject($item['id']);  
87 - $data = $this->getVideoInfo(); 95 + if($item['project_type'] == 1){
  96 + //todo::页面上获取数据
  97 + $data = $this->getAiVideoParam($item['id']);
  98 + }else{
  99 + //获取当前网站的标题
  100 + ProjectServer::useProject($item['id']);
  101 + $data = $this->getVideoInfo();
  102 + DB::disconnect('custom_mysql');
  103 + }
88 if(!empty($data)){ 104 if(!empty($data)){
89 //写入一条零时生成视频记录 105 //写入一条零时生成视频记录
90 $aiVideoAutoLogModel = new AiVideoAutoLog(); 106 $aiVideoAutoLogModel = new AiVideoAutoLog();
@@ -92,8 +108,8 @@ class AiVideoAutoPublish extends Command @@ -92,8 +108,8 @@ class AiVideoAutoPublish extends Command
92 ['project_id'=>$item['id'],'title'=>$data['title'],'remark'=>$data['remark'],'images'=>json_encode($data['images'],true),'date'=>date('Y-m-d')] 108 ['project_id'=>$item['id'],'title'=>$data['title'],'remark'=>$data['remark'],'images'=>json_encode($data['images'],true),'date'=>date('Y-m-d')]
93 ); 109 );
94 } 110 }
95 - DB::disconnect('custom_mysql');  
96 } 111 }
  112 + return true;
97 } 113 }
98 114
99 /** 115 /**
@@ -113,8 +129,19 @@ class AiVideoAutoPublish extends Command @@ -113,8 +129,19 @@ class AiVideoAutoPublish extends Command
113 return $data; 129 return $data;
114 } 130 }
115 $data['title'] = $info['title']; 131 $data['title'] = $info['title'];
116 - $data['remark'] = $info['intro'];  
117 - $data['images'] = array_walk($info['gallery'], function (&$item) {$item['alt'] = $item['title'];unset($item['title']);}); 132 + $data['remark'] = strip_tags($info['intro']);
  133 + if(empty($data['remark'])){
  134 + $data['remark'] = $data['title'];
  135 + }
  136 + $data['images'] = array_filter(array_map(function ($item) use ($data) {
  137 + if (!empty($item['url'])) {
  138 + return [
  139 + 'alt' => $item['title'] ?? $data['title'],
  140 + 'url' => getImageUrl($item['url']),
  141 + ];
  142 + }
  143 + return null;
  144 + }, $info['gallery']));
118 return $data; 145 return $data;
119 }else{ 146 }else{
120 //聚合页获取当前关联产品的图片 147 //聚合页获取当前关联产品的图片
@@ -123,13 +150,26 @@ class AiVideoAutoPublish extends Command @@ -123,13 +150,26 @@ class AiVideoAutoPublish extends Command
123 if(empty($keywordInfo)){ 150 if(empty($keywordInfo)){
124 return $data; 151 return $data;
125 } 152 }
126 - $data['title'] = $keywordInfo['title'];  
127 - $data['remark'] = $keywordInfo['intro']; 153 + $data['title'] = $keywordInfo['keyword_title'];
  154 + $data['remark'] = $keywordInfo['keyword_content'];
  155 + $data['remark'] = strip_tags($keywordInfo['intro']);
  156 + if(empty($data['remark'])){
  157 + $data['remark'] = $data['title'];
  158 + }
128 $data['images'] = []; 159 $data['images'] = [];
129 $productModel = new Product(); 160 $productModel = new Product();
130 $productList = $productModel->list(['keyword_id'=>['like','%,'.$keywordInfo['id'].',%']],'id',['gallery'],'desc',10); 161 $productList = $productModel->list(['keyword_id'=>['like','%,'.$keywordInfo['id'].',%']],'id',['gallery'],'desc',10);
131 foreach ($productList as $info){ 162 foreach ($productList as $info){
132 - $data['images'] = array_merge($data['images'],array_walk($info['gallery'], function (&$item) {$item['alt'] = $item['title'];unset($item['title']);})); 163 + $images = array_filter(array_map(function ($item) use ($data) {
  164 + if (!empty($item['url'])) {
  165 + return [
  166 + 'alt' => $item['title'] ?? $data['title'],
  167 + 'url' => getImageUrl($item['url']),
  168 + ];
  169 + }
  170 + return null; // 返回 null 让 array_filter 去除
  171 + }, $info['gallery']));
  172 + $data['images'] = array_merge($data['images'],$images);
133 } 173 }
134 return $data; 174 return $data;
135 } 175 }
@@ -147,20 +187,24 @@ class AiVideoAutoPublish extends Command @@ -147,20 +187,24 @@ class AiVideoAutoPublish extends Command
147 $aiVideoAutoLogModel = new AiVideoAutoLog(); 187 $aiVideoAutoLogModel = new AiVideoAutoLog();
148 while (true){ 188 while (true){
149 if($number > 5){ 189 if($number > 5){
  190 + echo date('Y-m-d H:i:s').':当前生成图片数量已达到最大限度。'.$number.PHP_EOL;
150 sleep(300); 191 sleep(300);
151 continue; 192 continue;
152 } 193 }
153 - $item = $aiVideoAutoLogModel->read(['status'=>0]);  
154 - if(empty($info)){ 194 + $item = $aiVideoAutoLogModel->read(['status'=>0,'trigger_id'=>null]);
  195 + if($item === false){
  196 + echo date('Y-m-d H:i:s').':无生成图片的数据。'.PHP_EOL;
155 sleep(60); 197 sleep(60);
156 continue; 198 continue;
157 } 199 }
158 if(count($item['images']) < 6){ 200 if(count($item['images']) < 6){
  201 + echo date('Y-m-d H:i:s').':提交生成图片。'.$item['project_id'].PHP_EOL;
159 //需要生成图片 202 //需要生成图片
160 $content = "{$item['remark']}{$item['title']}4K,高清 --no logo --ar 16:9"; 203 $content = "{$item['remark']}{$item['title']}4K,高清 --no logo --ar 16:9";
161 $midJourneyService = new MidJourneyService(); 204 $midJourneyService = new MidJourneyService();
162 $result = $midJourneyService->imagine($content); 205 $result = $midJourneyService->imagine($content);
163 if($result && !empty($result['trigger_id'])){ 206 if($result && !empty($result['trigger_id'])){
  207 + echo '提交的数据详情。'.json_encode($result,true).$item['project_id'].PHP_EOL;
164 Redis::incr('ai_video_image'); 208 Redis::incr('ai_video_image');
165 $aiVideoAutoLogModel->edit(['trigger_id'=>$result['trigger_id']],['id'=>$item['id']]); 209 $aiVideoAutoLogModel->edit(['trigger_id'=>$result['trigger_id']],['id'=>$item['id']]);
166 } 210 }
@@ -172,16 +216,89 @@ class AiVideoAutoPublish extends Command @@ -172,16 +216,89 @@ class AiVideoAutoPublish extends Command
172 } 216 }
173 217
174 /** 218 /**
175 - * @remark : 219 + * @remark :状态为1的数据推送至生成视频任务表
  220 + * @name :auto_save_video_task
  221 + * @author :lyh
  222 + * @method :post
  223 + * @time :2025/8/4 9:39
  224 + */
  225 + public function auto_save_video_task(){
  226 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  227 + while (true){
  228 + //获取任务id
  229 + $task_id = $this->getAutoTaskId();
  230 + if(empty($task_id)){
  231 + sleep(300);
  232 + continue;
  233 + }
  234 + $info = $aiVideoAutoLogModel->read(['id'=>$task_id]);
  235 + if($info === false){
  236 + $this->output(date('Y-m-d H:i:s').':当前数据不存在或已被删除'.$task_id);
  237 + continue;
  238 + }
  239 + try {
  240 + $aiVideoTaskModel = new AiVideoTask();
  241 + $aiVideoService = new AiVideoService($info['project_id']);
  242 + $projectModel = new DeployOptimize();
  243 + $video_setting = $projectModel->getValue(['project_id'=>$info['project_id']],'video_setting');
  244 + $storage = $aiVideoTaskModel->videoSetting()[$video_setting ?? 1];
  245 + $result = $aiVideoService->createTask($info['title'],$info['remark'],$info['images'],[],$storage);
  246 + if($result['status'] == 200){
  247 + $aiVideoTaskModel->addReturnId(['task_id'=>$result['data']['task_id'],'project_id'=>$info['project_id'],'storage'=>$storage]);
  248 + ProjectServer::useProject($info['project_id']);
  249 + $aiVideoModel = new AiVideo();
  250 + $next_auto_date = date('Y-m-d', strtotime('+' . mt_rand(5,7) . 'days')); //每5-7天自动发布
  251 + $aiVideoModel->addReturnId(['next_auto_date'=>$next_auto_date,'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)]);
  252 + DB::disconnect('custom_mysql');
  253 + $aiVideoAutoLogModel->edit(['status'=>2],['id'=>$info['id']]);
  254 + }
  255 + }catch (\Exception $e){
  256 + $this->output( date('Y-m-d H:i:s').':当前数据不存在或已被删除'.$task_id);
  257 + continue;
  258 + }
  259 + }
  260 + }
  261 +
  262 + /**
  263 + * @remark :火锅自动发布任务id
  264 + * @name :getAutoTaskId
  265 + * @author :lyh
  266 + * @method :post
  267 + * @time :2025/8/4 9:44
  268 + */
  269 + public function getAutoTaskId()
  270 + {
  271 + $task_id = Redis::rpop('auto_ai_video_task');
  272 + if (empty($task_id)) {
  273 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  274 + $ids = $aiVideoAutoLogModel->formatQuery(['status'=>1])->pluck('id');
  275 + if(!empty($ids)){
  276 + foreach ($ids as $id) {
  277 + Redis::lpush('auto_ai_video_task', $id);
  278 + }
  279 + }
  280 + $task_id = Redis::rpop('auto_ai_video_task');
  281 + }
  282 + return $task_id;
  283 + }
  284 +
  285 + /**
  286 + * @remark :页面获取
176 * @name :getAiVideoParam 287 * @name :getAiVideoParam
177 * @author :lyh 288 * @author :lyh
178 * @method :post 289 * @method :post
179 * @time :2025/8/1 16:25 290 * @time :2025/8/1 16:25
180 */ 291 */
181 - public function getAiVideoParam() 292 + public function getAiVideoParam($project_id = 3751)
182 { 293 {
183 - $project_id = '1';  
184 - $domain = 'www.cs-conveyor.com'; 294 + //获取当前网站域名
  295 + $domainModel = new DomainInfo();
  296 + $domain = $domainModel->getValue(['project_id'=>$project_id],'domain');
  297 + if(empty($domain)){
  298 + return true;
  299 + }
  300 + $domain = str_replace('blog.', 'www.', $domain);
  301 + //todo::看是否获取建站的产品数据
185 try { 302 try {
186 $sitemap_url = 'https://' . $domain . '/sitemap_post_tag.xml'; 303 $sitemap_url = 'https://' . $domain . '/sitemap_post_tag.xml';
187 $sitemap_string = file_get_contents($sitemap_url); 304 $sitemap_string = file_get_contents($sitemap_url);
@@ -203,16 +320,20 @@ class AiVideoAutoPublish extends Command @@ -203,16 +320,20 @@ class AiVideoAutoPublish extends Command
203 $img = $dom->find('.layout .global_section img'); 320 $img = $dom->find('.layout .global_section img');
204 $images = []; 321 $images = [];
205 foreach ($img as $item) { 322 foreach ($img as $item) {
206 - if (empty($item->src) || empty($item->alt)) 323 + if (empty($item->src) || empty($item->alt)){
207 continue; 324 continue;
208 - array_push($images, ['src' => $item->src, 'alt' => $item->alt]); 325 + }
  326 + array_push($images, ['url' => $item->src, 'alt' => $item->alt]);
209 } 327 }
210 if (empty($title) || empty($content) || empty($images)) { 328 if (empty($title) || empty($content) || empty($images)) {
211 $num++; 329 $num++;
212 goto AGAIN; 330 goto AGAIN;
213 } 331 }
  332 + return ['title'=>$title,'remark'=>$content,'images'=>$images];
214 } catch (\Exception $e) { 333 } catch (\Exception $e) {
  334 + $this->output('project_id: ' . $project_id . ', domain: ' . $domain . ', error: ' . $e->getMessage());
215 echo 'project_id: ' . $project_id . ', domain: ' . $domain . ', error: ' . $e->getMessage() . PHP_EOL; 335 echo 'project_id: ' . $project_id . ', domain: ' . $domain . ', error: ' . $e->getMessage() . PHP_EOL;
  336 + return [];
216 } 337 }
217 } 338 }
218 339
@@ -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 /**
@@ -55,10 +55,10 @@ class WorkchatMessageSend extends Command @@ -55,10 +55,10 @@ class WorkchatMessageSend extends Command
55 $task->status = MessagePush::STATUS_ERROR; 55 $task->status = MessagePush::STATUS_ERROR;
56 $task->remark = '请求超时'; 56 $task->remark = '请求超时';
57 } 57 }
58 - }catch (\Exception $e){  
59 - $this->output('推送消息' . $task->id . '失败:' . $e->getMessage()); 58 + } catch (\Exception $e) {
  59 + $this->output('推送消息' . $task->id . '失败:' . $e->getLine() . ' - ' . $e->getMessage());
60 $task->status = MessagePush::STATUS_ERROR; 60 $task->status = MessagePush::STATUS_ERROR;
61 - $task->remark = mb_substr($e->getMessage(), 0 , 200); 61 + $task->remark = mb_substr($e->getMessage(), 0, 200);
62 } 62 }
63 $task->save(); 63 $task->save();
64 } 64 }
@@ -11,6 +11,8 @@ namespace App\Http\Controllers\Api; @@ -11,6 +11,8 @@ 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\Redis;
14 16
15 class AiVideoController extends BaseController 17 class AiVideoController extends BaseController
16 { 18 {
@@ -22,10 +24,59 @@ class AiVideoController extends BaseController @@ -22,10 +24,59 @@ class AiVideoController extends BaseController
22 * @time :2025/8/2 11:19 24 * @time :2025/8/2 11:19
23 */ 25 */
24 public function ImageCallBack(){ 26 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']]); 27 + $this->saveLog('数据详情:'.json_encode($this->param,true));
  28 + $count = Redis::decr('ai_video_image');
  29 + if ($count < 0) {
  30 + Redis::set('ai_video_image', 0);
  31 + }
  32 + $data = $this->param['attachments'][0] ?? [];
  33 + $aiVideoAutoLogModel = new AiVideoAutoLog();
  34 + if(empty($data) || empty($data['url'])){
  35 + $this->saveLog('未获取到回调数据详情');
  36 + $aiVideoAutoLogModel->edit(['status'=>9,'result'=>json_encode($this->param,true)],['trigger_id'=>$this->param['id']]);
  37 + }
  38 + //获取当前数据详情
  39 + $info = $aiVideoAutoLogModel->read(['trigger_id'=>$this->param['trigger_id']]);
  40 + if($info === false){
  41 + $this->saveLog('未获取到当前数据详情。'.json_encode($info,true));
  42 + $this->response('success');
  43 + }
  44 + //上传图片 返回cdn链接
  45 + $cosService = new CosService();
  46 + $imagePath = $cosService->uploadRemote($info['project_id'],'video',$data['url']);
  47 + $this->saveLog($imagePath);
  48 + try {
  49 + if($imagePath){
  50 + $cos = config('filesystems.disks.cos');
  51 + $url = $cos['cdn1'].'/'.$imagePath;
  52 + //裁剪图片为4张
  53 + $images = [];
  54 + $cosService = new CosService();
  55 + $data = $cosService->cropAndUploadToCOS($url);
  56 + if(!empty($data)){
  57 + foreach ($data as $item){
  58 + $images[] = ['url'=>$item,'alt'=>$info['title']];
  59 + }
  60 + }
  61 + $images = array_merge($images,$info['images']);
  62 + $this->saveLog('success->ok');
  63 + $aiVideoAutoLogModel->edit(['images'=>$images,'result'=>json_encode($this->param,true),'status'=>1],['id'=>$info['id']]);
  64 + }
  65 + }catch (\Exception $e){
  66 + $this->saveLog('上传图片失败,获取到数据详情。'.$e->getMessage());
  67 + }
29 $this->response('success'); 68 $this->response('success');
30 } 69 }
  70 +
  71 + /**
  72 + * @remark :保存日志
  73 + * @name :saveLog
  74 + * @author :lyh
  75 + * @method :post
  76 + * @time :2025/8/4 11:54
  77 + */
  78 + public function saveLog($message){
  79 + @file_put_contents(storage_path('logs/ai_video.log'), var_export($message, true) . PHP_EOL, FILE_APPEND);
  80 + return true;
  81 + }
31 } 82 }
@@ -32,7 +32,11 @@ class DomainInfoController extends BaseController @@ -32,7 +32,11 @@ class DomainInfoController extends BaseController
32 if(isset($this->map['domain']) && !empty($this->map['domain'])){ 32 if(isset($this->map['domain']) && !empty($this->map['domain'])){
33 $this->map['domain'] = ['like','%'.$this->map['domain'].'%']; 33 $this->map['domain'] = ['like','%'.$this->map['domain'].'%'];
34 } 34 }
35 - $lists = $domainModel->lists($this->map,$this->page,$this->row,$this->order); 35 + if(isset($this->map['sort']) && !empty($this->map['sort'])){
  36 + $sort = $this->map['sort'];
  37 + unset($this->map['sort']);
  38 + }
  39 + $lists = $domainModel->lists($this->map,$this->page,$this->row,$this->order,['*'],$sort ?? 'desc');
36 if(!empty($lists)){ 40 if(!empty($lists)){
37 $project_model = new Project(); 41 $project_model = new Project();
38 $serverIpModel = new ServersIp(); 42 $serverIpModel = new ServersIp();
@@ -10,6 +10,7 @@ use App\Http\Logic\Aside\Project\OnlineCheckLogic; @@ -10,6 +10,7 @@ use App\Http\Logic\Aside\Project\OnlineCheckLogic;
10 use App\Http\Logic\Aside\Project\ProcessRecordsLogic; 10 use App\Http\Logic\Aside\Project\ProcessRecordsLogic;
11 use App\Http\Logic\Aside\Project\ProjectLogic; 11 use App\Http\Logic\Aside\Project\ProjectLogic;
12 use App\Http\Requests\Aside\Project\ProcessRecordsRequest; 12 use App\Http\Requests\Aside\Project\ProcessRecordsRequest;
  13 +use App\Models\Ai\AiVideo;
13 use App\Models\ASide\APublicModel; 14 use App\Models\ASide\APublicModel;
14 use App\Models\Channel\Channel; 15 use App\Models\Channel\Channel;
15 use App\Models\Channel\User; 16 use App\Models\Channel\User;
@@ -27,7 +28,9 @@ use App\Models\Industry\ProjectIndustry; @@ -27,7 +28,9 @@ use App\Models\Industry\ProjectIndustry;
27 use App\Models\Inquiry\InquirySet; 28 use App\Models\Inquiry\InquirySet;
28 use App\Models\Manage\BelongingGroup; 29 use App\Models\Manage\BelongingGroup;
29 use App\Models\Manage\ManageHr; 30 use App\Models\Manage\ManageHr;
  31 +use App\Models\Project\AiVideoTask;
30 use App\Models\Project\DeployBuild; 32 use App\Models\Project\DeployBuild;
  33 +use App\Models\Project\DeployOptimize;
31 use App\Models\Project\Payment; 34 use App\Models\Project\Payment;
32 use App\Models\Project\ProcessRecords; 35 use App\Models\Project\ProcessRecords;
33 use App\Models\Project\Project; 36 use App\Models\Project\Project;
@@ -1285,4 +1288,16 @@ class ProjectController extends BaseController @@ -1285,4 +1288,16 @@ class ProjectController extends BaseController
1285 $this->response('success',Code::SUCCESS,['url'=>$this->param['url']]); 1288 $this->response('success',Code::SUCCESS,['url'=>$this->param['url']]);
1286 } 1289 }
1287 1290
  1291 + /**
  1292 + * @remark :获取视频配置类型
  1293 + * @name :videoSetting
  1294 + * @author :lyh
  1295 + * @method :post
  1296 + * @time :2025/8/5 9:50
  1297 + */
  1298 + public function videoSetting(){
  1299 + $videoModel = new AiVideoTask();
  1300 + $data = $videoModel->videoSetting();
  1301 + $this->response('success',Code::SUCCESS,$data);
  1302 + }
1288 } 1303 }
@@ -191,7 +191,7 @@ class ProjectAssociationServices extends BaseService @@ -191,7 +191,7 @@ class ProjectAssociationServices extends BaseService
191 $url = 'https://hub.ai.cc/api/globalso_ai_customer_service/send_msg'; 191 $url = 'https://hub.ai.cc/api/globalso_ai_customer_service/send_msg';
192 $result = Http::withoutVerifying()->withOptions(['proxy' => env('CURL_PROXY')])->timeout(30)->post($url, $param)->json(); 192 $result = Http::withoutVerifying()->withOptions(['proxy' => env('CURL_PROXY')])->timeout(30)->post($url, $param)->json();
193 if(empty($result) || $result['status'] != 200){ 193 if(empty($result) || $result['status'] != 200){
194 - throw new \Exception($result['message'] ?? ''); 194 + throw new \Exception(json_encode($result));
195 } 195 }
196 return true; 196 return true;
197 } 197 }
@@ -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 }
@@ -205,6 +205,7 @@ Route::middleware(['aloginauth'])->group(function () { @@ -205,6 +205,7 @@ Route::middleware(['aloginauth'])->group(function () {
205 Route::any('/setIsParticiple', [Aside\Project\ProjectController::class, 'setIsParticiple'])->name('admin.project_setIsParticiple');//开启/关闭分词 205 Route::any('/setIsParticiple', [Aside\Project\ProjectController::class, 'setIsParticiple'])->name('admin.project_setIsParticiple');//开启/关闭分词
206 Route::any('/saveSiteStatus', [Aside\Project\ProjectController::class, 'saveSiteStatus'])->name('admin.project_saveSiteStatus'); 206 Route::any('/saveSiteStatus', [Aside\Project\ProjectController::class, 'saveSiteStatus'])->name('admin.project_saveSiteStatus');
207 Route::any('/updateTdk', [Aside\Project\ProjectController::class, 'updateTdk'])->name('admin.project_updateTdk');//更新项目tdk 207 Route::any('/updateTdk', [Aside\Project\ProjectController::class, 'updateTdk'])->name('admin.project_updateTdk');//更新项目tdk
  208 + Route::any('/videoSetting', [Aside\Project\ProjectController::class, 'videoSetting'])->name('admin.project_videoSetting');//项目管理AI.video设置
208 //获取关键词前缀和后缀 209 //获取关键词前缀和后缀
209 Route::prefix('keyword')->group(function () { 210 Route::prefix('keyword')->group(function () {
210 Route::any('/getKeywordPrefix', [Aside\Project\KeywordPrefixController::class, 'getKeywordPrefix'])->name('admin.keyword_getKeywordPrefix'); 211 Route::any('/getKeywordPrefix', [Aside\Project\KeywordPrefixController::class, 'getKeywordPrefix'])->name('admin.keyword_getKeywordPrefix');