作者 赵彬吉

update

  1 +<?php
  2 +
  3 +namespace App\Console\Commands\Tdk;
  4 +
  5 +use App\Helper\Arr;
  6 +use App\Helper\Common;
  7 +use App\Helper\Gpt;
  8 +use App\Models\Ai\AiCommand;
  9 +use App\Models\Domain\DomainInfo;
  10 +use App\Models\Mail\Mail;
  11 +use App\Models\Project\DeployBuild;
  12 +use App\Models\Project\DeployOptimize;
  13 +use App\Models\Project\ProjectUpdateTdk;
  14 +use App\Models\User\User;
  15 +use App\Models\WebSetting\WebLanguage;
  16 +use App\Services\ProjectServer;
  17 +use Illuminate\Console\Command;
  18 +use Illuminate\Support\Facades\Cache;
  19 +use Illuminate\Support\Facades\DB;
  20 +use Illuminate\Support\Facades\Redis;
  21 +use Illuminate\Support\Str;
  22 +
  23 +/**
  24 + * 指定跑某个项目
  25 + * Class InitProject
  26 + * @package App\Console\Commands
  27 + * @author zbj
  28 + * @date 2023/10/8
  29 + */
  30 +class UpdateSeoTdkByTaskId extends Command
  31 +{
  32 + /**
  33 + * The name and signature of the console command.
  34 + *
  35 + * @var string
  36 + */
  37 + protected $signature = 'update_seo_tdk_by {task_id}';
  38 +
  39 + /**
  40 + * The console command description.
  41 + *
  42 + * @var string
  43 + */
  44 + protected $description = '一键生成tdk';
  45 +
  46 + protected $project;
  47 +
  48 + /**
  49 + * Create a new command instance.
  50 + *
  51 + * @return void
  52 + */
  53 + public function __construct()
  54 + {
  55 + parent::__construct();
  56 + }
  57 +
  58 + /**
  59 + * '表' => [
  60 + * '指令key' => '表字段'
  61 + * ]
  62 + * @return array
  63 + * @author zbj
  64 + * @date 2023/11/3
  65 + */
  66 + protected $maps = [
  67 + 'gl_web_custom_template' => [
  68 + 'page_title' => 'title',
  69 + 'page_meta_keywords' => 'keywords',
  70 + 'page_meta_description' => 'description',
  71 + ],
  72 + 'gl_product' => [
  73 + 'product_title' => 'seo_mate.title',
  74 + 'product_meta_keywords' => 'seo_mate.keyword',
  75 + 'product_meta_description' => 'seo_mate.description',
  76 + ],
  77 + 'gl_product_category' => [
  78 + 'product_cat_title' => 'seo_title',
  79 + 'product_cat_meta_keywords' => 'seo_keywords',
  80 + 'product_cat_meta_description' => 'seo_des',
  81 + ],
  82 + 'gl_blog' => [
  83 + 'blog_title' => 'seo_title',
  84 + 'blog_meta_keywords' => 'seo_keywords',
  85 + 'blog_meta_description' => 'seo_description',
  86 + ],
  87 + 'gl_blog_category' => [
  88 + 'blog_cat_title' => 'seo_title',
  89 + 'blog_cat_meta_keywords' => 'seo_keywords',
  90 + 'blog_cat_meta_description' => 'seo_des',
  91 + ],
  92 + 'gl_news' => [
  93 + 'news_title' => 'seo_title',
  94 + 'news_meta_keywords' => 'seo_keywords',
  95 + 'news_meta_description' => 'seo_description',
  96 + ],
  97 + 'gl_news_category' => [
  98 + 'news_cat_title' => 'seo_title',
  99 + 'news_cat_meta_keywords' => 'seo_keywords',
  100 + 'news_cat_meta_description' => 'seo_des',
  101 + ],
  102 + 'gl_product_keyword' => [
  103 + 'tags_title' => 'seo_title',
  104 + 'tags_meta_keywords' => 'seo_keywords',
  105 + 'tags_meta_description' => 'seo_description',
  106 + 'tags_content_title' => 'keyword_title',
  107 + 'tags_content_description' => 'keyword_content',
  108 + ]
  109 + ];
  110 +
  111 + /**
  112 + * topic-对相应字段
  113 + * @var array
  114 + */
  115 + protected $topic_fields = [
  116 + 'gl_web_custom_template' => 'name',
  117 + 'gl_product' => 'title',
  118 + 'gl_product_category' => 'title',
  119 + 'gl_blog' => 'name',
  120 + 'gl_blog_category' => 'name',
  121 + 'gl_news' => 'name',
  122 + 'gl_news_category' => 'name',
  123 + 'gl_product_keyword' => 'title'
  124 + ];
  125 +
  126 + /**
  127 + * 使用核心关键词的key 数量
  128 + * @var array
  129 + */
  130 + protected $core_keyword_keys = [
  131 + 'page_meta_keywords' => 1,
  132 + 'blog_cat_meta_keywords' => 8,
  133 + 'news_cat_meta_keywords' => 8,
  134 + ];
  135 +
  136 + /**
  137 + * @return bool
  138 + */
  139 + public function handle()
  140 + {
  141 + $task_id = $this->argument('task_id');
  142 + $task = ProjectUpdateTdk::where('status', ProjectUpdateTdk::STATUS_PENDING)->where('id', $task_id)->first();
  143 + if (!$task) {
  144 + echo date('Y-m-d H:i:s') . ' 任务不存在: ' . $task_id . PHP_EOL;
  145 + }
  146 + $project_id = $task->project_id;
  147 +
  148 + echo date('Y-m-d H:i:s') . ' start project_id: ' . $project_id . PHP_EOL;
  149 + try {
  150 + $this->project = ProjectServer::useProject($project_id);
  151 + $this->seo_tdk($project_id, $task->id);
  152 + DB::disconnect('custom_mysql');
  153 + }catch (\Exception $e){
  154 + echo date('Y-m-d H:i:s') . 'line: '. $e->getLine() .' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
  155 + ProjectUpdateTdk::retry($task->id, $e->getMessage());
  156 + }
  157 + Cache::forget('project_deploy_optimize_info_' . $project_id);
  158 + echo date('Y-m-d H:i:s') . ' end project_id: ' . $project_id . PHP_EOL;
  159 + }
  160 + public function sendNotify($project_id, $route)
  161 + {
  162 + //获取当前项目的域名
  163 + $domainModel = new DomainInfo();
  164 + $domainInfo = $domainModel->read(['project_id'=>$project_id]);
  165 + if($domainInfo === false){
  166 + //获取测试域名
  167 + $deployBuildModel = new DeployBuild();
  168 + $buildInfo = $deployBuildModel->read(['project_id'=>$project_id]);
  169 + $this->param['domain'] = $buildInfo['test_domain'];
  170 + }else{
  171 + $this->param['domain'] = 'https://'.$domainInfo['domain'].'/';
  172 + }
  173 + $url = $this->param['domain'].'api/update_page/';
  174 + $param = [
  175 + 'project_id' => $project_id,
  176 + 'type' => 1,
  177 + 'route' => $route,
  178 + 'url' => [],
  179 + 'language'=> [],
  180 + ];
  181 + http_post($url, json_encode($param));
  182 + echo '更新中请稍后, 更新完成将会发送站内信通知更新结果!'. PHP_EOL;
  183 + }
  184 + public function seo_tdk($project_id, $task_id)
  185 + {
  186 + $notify_master = $notify_keyword = false;
  187 + //更新统计
  188 + $update = [];
  189 + $ai_commands = AiCommand::where('is_batch', 1)->select('key', 'scene', 'ai')->get()->toArray();
  190 + $ai_commands = Arr::setValueToKey($ai_commands, 'key');
  191 + foreach ($this->maps as $table => $map) {
  192 + $total_page = DB::connection('custom_mysql')->table($table)->count();
  193 + $update[$table] = ['total_page'=>$total_page, 'title'=>0, 'keyword'=>0, 'des'=>0,'keyword_title'=>0,'keyword_content'=>0];
  194 + echo date('Y-m-d H:i:s') . '更新--' . $table . ': 项目id' . $project_id . PHP_EOL;
  195 + $list = DB::connection('custom_mysql')->table($table)
  196 + ->where(function ($query) use ($table, $map){
  197 + if($table == 'gl_product'){
  198 + foreach ($map as $field){
  199 + $field_arr = explode('.', $field);
  200 + $query->orWhereRaw('JSON_CONTAINS('.$field_arr[0].', "null", "$.'.$field_arr[1].'") OR JSON_EXTRACT('.$field_arr[0].', "$.'.$field_arr[1].'") = ""');
  201 + }
  202 + }else{
  203 + foreach ($map as $field){
  204 + $query->orWhereRaw($field . " IS NULL OR ".$field." = ''");
  205 + }
  206 + }
  207 + })->select('id')->get();
  208 + if (!empty($list)) {
  209 + $list = $list->toArray();
  210 + foreach ($list as $v) {
  211 + $v = (array)$v;
  212 +
  213 + //缓存 在处理的项目数据 id
  214 + $cache_key = "seo_tdk_{$project_id}_{$table}_{$v['id']}";
  215 + if(!Redis::setnx($cache_key, 1)){
  216 + continue;
  217 + }
  218 + Redis::expire($cache_key, 120);
  219 +
  220 + echo date('Y-m-d H:i:s') . '更新--' . $table . ': 项目id' . $project_id . ':id' . $v['id'] . PHP_EOL;
  221 + $v = DB::connection('custom_mysql')->table($table)->where('id', $v['id'])->first();
  222 + $v = (array)$v;
  223 + $data = [];
  224 + $json_field = '';
  225 + foreach ($map as $ai_key => $field) {
  226 + $field_arr = explode('.', $field);
  227 + if (count($field_arr) > 1) {
  228 + $json_field = $field_arr[0];
  229 + $value = json_decode($v[$field_arr[0]], true)[$field_arr[1]] ?? '';
  230 + } else {
  231 + $value = $v[$field] ?? '';
  232 + }
  233 + //已有值的 跳过
  234 + if ($value) {
  235 + echo $field.'已有值 跳过' . PHP_EOL;
  236 + continue;
  237 + }
  238 +
  239 + //AI生成
  240 + if (!empty($ai_commands[$ai_key]['ai'])) {
  241 + $prompt = $this->getPrompt($project_id, $ai_commands[$ai_key]['ai'], $table, $v);
  242 + if(!$prompt){
  243 + continue;
  244 + }
  245 + if (count($field_arr) > 1) {
  246 + if($field_arr[1] == 'title'){
  247 + $update[$table]['title']++;
  248 + }elseif ($field_arr[1] == 'keyword'){
  249 + $update[$table]['keyword']++;
  250 + }elseif ($field_arr[1] == 'description'){
  251 + $update[$table]['des']++;
  252 + }
  253 + $data[$field_arr[0]][$field_arr[1]] = $this->ai_send($prompt);
  254 + }else{
  255 + if($field == 'title' || $field == 'seo_title'){
  256 + $update[$table]['title']++;
  257 + }
  258 + if($field == 'keywords' || $field == 'seo_keywords'){
  259 + $update[$table]['keyword']++;
  260 + }
  261 + if($field == 'seo_description' || $field == 'description' || $field == 'seo_des'){
  262 + $update[$table]['des']++;
  263 + }
  264 + if($field == 'keyword_title'){
  265 + $update[$table]['keyword_title']++;
  266 + }
  267 + if($field == 'keyword_content'){
  268 + $update[$table]['keyword_content']++;
  269 + }
  270 + $data[$field] = $this->ai_send($prompt);
  271 + }
  272 + } else {
  273 + //直接使用topic
  274 + if (count($field_arr) > 1) {
  275 + $data[$field_arr[0]][$field_arr[1]] = $v[$this->topic_fields[$table]] ?? '';
  276 + //使用核心关键词
  277 + if(in_array($ai_key, array_keys($this->core_keyword_keys))){
  278 + $data[$field_arr[0]][$field_arr[1]] = $this->mainKeywords($project_id, $this->core_keyword_keys[$ai_key]);
  279 + if(!empty($data[$field_arr[0]][$field_arr[1]])){
  280 + if($field_arr[1] == 'title'){
  281 + $data[$table]['title']++;
  282 + }elseif ($field_arr[1] == 'keyword'){
  283 + $data[$table]['keyword']++;
  284 + }elseif ($field_arr[1] == 'description'){
  285 + $data[$table]['des']++;
  286 + }
  287 + }
  288 + }
  289 + }else{
  290 + $data[$field] = $v[$this->topic_fields[$table]] ?? '';
  291 + //使用核心关键词
  292 + if(in_array($ai_key, array_keys($this->core_keyword_keys))){
  293 + $data[$field] = $this->mainKeywords($project_id, $this->core_keyword_keys[$ai_key]);
  294 + if(!empty($data[$field])){
  295 + if($field == 'title' || $field == 'seo_title'){
  296 + $update[$table]['title']++;
  297 + }
  298 + if($field == 'keywords' || $field == 'seo_keywords'){
  299 + $update[$table]['keyword']++;
  300 + }
  301 + if($field == 'seo_description' || $field == 'description' || $field == 'seo_des'){
  302 + $update[$table]['des']++;
  303 + }
  304 + if($field == 'keyword_title'){
  305 + $update[$table]['keyword_title']++;
  306 + }
  307 + if($field == 'keyword_content'){
  308 + $update[$table]['keyword_content']++;
  309 + }
  310 + }
  311 + }
  312 + }
  313 + }
  314 + }
  315 + if(!$data){
  316 + continue;
  317 + }
  318 + if($json_field){
  319 + $old_data = json_decode($v[$field_arr[0]], true);
  320 + foreach ($old_data ?: [] as $kk=>$vv){
  321 + empty($data[$json_field][$kk]) && $data[$json_field][$kk] = $vv;
  322 + }
  323 + $data[$json_field] = json_encode($data[$json_field]);
  324 + }
  325 + DB::connection('custom_mysql')->table($table)->where(['id' => $v['id']])->update($data);
  326 + if($table == 'gl_product_keyword'){
  327 + $notify_keyword = true;
  328 + }else{
  329 + $notify_master = true;
  330 + }
  331 + }
  332 + }
  333 + }
  334 + ProjectUpdateTdk::finish($task_id, $update);
  335 +
  336 + //通知C端更新界面
  337 + $notify_master && $this->sendNotify($project_id, 1); //通知主站更新
  338 + $notify_keyword && $this->sendNotify($project_id, 4); //通知聚合页更新
  339 + }
  340 +
  341 + public function getPrompt($project_id, $prompt, $table, $data){
  342 + if(strpos($prompt, '{topic}') !== false){
  343 + $topic = $data[$this->topic_fields[$table]] ?? '';
  344 + if(!$topic){
  345 + echo 'topic为空 跳过' . PHP_EOL;
  346 + return false;
  347 + }
  348 + $prompt = str_replace('{topic}', $topic, $prompt);
  349 + }
  350 + if(strpos($prompt, '{keyword}') !== false) {
  351 + $keyword = $this->mainKeywords($project_id, 1);
  352 + if(!$keyword){
  353 + echo '核心关键词为空 跳过' . PHP_EOL;
  354 + return false;
  355 + }
  356 + $prompt = str_replace('{keyword}', $keyword, $prompt);
  357 + }
  358 + if(strpos($prompt, '{company name}') !== false) {
  359 + $company_name = $this->companyName($project_id);
  360 + if(!$company_name){
  361 + echo '公司英文全称为空 跳过' . PHP_EOL;
  362 + return false;
  363 + }
  364 + $prompt = str_replace('{company name}', $company_name, $prompt);
  365 + }
  366 + $prompt .= '.Please answer in ' . $this->getLang();
  367 + return $prompt;
  368 + }
  369 +
  370 +
  371 + public function getDeployOptimize($project_id){
  372 + $cache_key = 'project_deploy_optimize_info_' . $project_id;
  373 + $info = Cache::get($cache_key);
  374 + if(!$info){
  375 + $projectOptimizeModel = new DeployOptimize();
  376 + $info = $projectOptimizeModel->read(['project_id' => $project_id], ['id', 'company_en_name', 'company_en_description', 'main_keywords']);
  377 + Cache::put($cache_key, $info, 600);
  378 + }
  379 + return $info;
  380 + }
  381 +
  382 + /**
  383 + * @remark :获取公司英文名称
  384 + * @name :companyName
  385 + * @author :lyh
  386 + * @method :post
  387 + * @time :2023/10/30 11:22
  388 + */
  389 + public function companyName($project_id, $key = '')
  390 + {
  391 + $data = [
  392 + 'product_long_description',
  393 + ];
  394 + $info = $this->getDeployOptimize($project_id);
  395 + if (in_array($key, $data)) {
  396 + return $info['company_en_description'];
  397 + } else {
  398 + return $info['company_en_name'];
  399 + }
  400 + }
  401 +
  402 + /**
  403 + * @remark :获取公司核心关键词
  404 + * @name :mainKeywords
  405 + * @author :lyh
  406 + * @method :post
  407 + * @time :2023/10/30 11:22
  408 + */
  409 + public function mainKeywords($project_id, $num)
  410 + {
  411 + $str = '';
  412 + $info = $this->getDeployOptimize($project_id);
  413 + if (!empty($info['main_keywords'])) {
  414 + $main_keywords = explode("\r\n", $info['main_keywords']);
  415 + //随机取
  416 + shuffle($main_keywords);
  417 + $main_keywords = array_slice($main_keywords, 0, $num);
  418 + $str = implode(", ", $main_keywords);
  419 + }
  420 + return $str;
  421 + }
  422 +
  423 + public function getLang(){
  424 + $lang = WebLanguage::getLangById($this->project['main_lang_id']??1);
  425 + return $lang['english'] ?? 'English';
  426 + }
  427 +
  428 + /**
  429 + * @remark :AI发送
  430 + * @name :ai_send
  431 + * @author :lyh
  432 + * @method :post
  433 + * @time :2023/8/19 10:40
  434 + */
  435 + public function ai_send($prompt)
  436 + {
  437 + $text = Gpt::instance()->openai_chat_qqs($prompt);
  438 + $text = Common::deal_keywords($text);
  439 + $text = Common::deal_str($text);
  440 +
  441 + //包含这写字 重新生成
  442 + if(Str::contains(Str::lower($text), ['[your brand]', '[brand name]'])){
  443 + return $this->ai_send($prompt);
  444 + }
  445 +
  446 + return $text;
  447 + }
  448 +
  449 + /**
  450 + * @remark :发送站内信
  451 + * @name :send_message
  452 + * @author :lyh
  453 + * @method :post
  454 + * @time :2023/11/4 10:22
  455 + */
  456 + public function send_message($project_id){
  457 + $user = new User();
  458 + $userInfo = $user->read(['project_id'=>$project_id,'role_id'=>0]);
  459 + $data["title"] = "seo更新通知---完成";
  460 + $data["user_list"] = $userInfo['id'];
  461 + $data["content"] = "seo更新成功,更新完成时间".date('Y-m-d H:i:s');
  462 + $mail = new Mail();
  463 + return $mail->add($data);
  464 + }
  465 +}