|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Console\Commands\Tdk;
|
|
|
|
|
|
|
|
use App\Helper\Arr;
|
|
|
|
use App\Helper\Common;
|
|
|
|
use App\Helper\Gpt;
|
|
|
|
use App\Models\Ai\AiCommand;
|
|
|
|
use App\Models\Domain\DomainInfo;
|
|
|
|
use App\Models\Mail\Mail;
|
|
|
|
use App\Models\Project\DeployBuild;
|
|
|
|
use App\Models\Project\DeployOptimize;
|
|
|
|
use App\Models\Project\ProjectUpdateTdk;
|
|
|
|
use App\Models\User\User;
|
|
|
|
use App\Models\WebSetting\WebLanguage;
|
|
|
|
use App\Services\ProjectServer;
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 指定跑某个项目
|
|
|
|
* Class InitProject
|
|
|
|
* @package App\Console\Commands
|
|
|
|
* @author zbj
|
|
|
|
* @date 2023/10/8
|
|
|
|
*/
|
|
|
|
class UpdateSeoTdkByTaskId extends Command
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The name and signature of the console command.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $signature = 'update_seo_tdk_by {task_id}';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The console command description.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $description = '一键生成tdk';
|
|
|
|
|
|
|
|
protected $project;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new command instance.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* '表' => [
|
|
|
|
* '指令key' => '表字段'
|
|
|
|
* ]
|
|
|
|
* @return array
|
|
|
|
* @author zbj
|
|
|
|
* @date 2023/11/3
|
|
|
|
*/
|
|
|
|
protected $maps = [
|
|
|
|
'gl_web_custom_template' => [
|
|
|
|
'page_title' => 'title',
|
|
|
|
'page_meta_keywords' => 'keywords',
|
|
|
|
'page_meta_description' => 'description',
|
|
|
|
],
|
|
|
|
'gl_product' => [
|
|
|
|
'product_title' => 'seo_mate.title',
|
|
|
|
'product_meta_keywords' => 'seo_mate.keyword',
|
|
|
|
'product_meta_description' => 'seo_mate.description',
|
|
|
|
],
|
|
|
|
'gl_product_category' => [
|
|
|
|
'product_cat_title' => 'seo_title',
|
|
|
|
'product_cat_meta_keywords' => 'seo_keywords',
|
|
|
|
'product_cat_meta_description' => 'seo_des',
|
|
|
|
],
|
|
|
|
'gl_blog' => [
|
|
|
|
'blog_title' => 'seo_title',
|
|
|
|
'blog_meta_keywords' => 'seo_keywords',
|
|
|
|
'blog_meta_description' => 'seo_description',
|
|
|
|
],
|
|
|
|
'gl_blog_category' => [
|
|
|
|
'blog_cat_title' => 'seo_title',
|
|
|
|
'blog_cat_meta_keywords' => 'seo_keywords',
|
|
|
|
'blog_cat_meta_description' => 'seo_des',
|
|
|
|
],
|
|
|
|
'gl_news' => [
|
|
|
|
'news_title' => 'seo_title',
|
|
|
|
'news_meta_keywords' => 'seo_keywords',
|
|
|
|
'news_meta_description' => 'seo_description',
|
|
|
|
],
|
|
|
|
'gl_news_category' => [
|
|
|
|
'news_cat_title' => 'seo_title',
|
|
|
|
'news_cat_meta_keywords' => 'seo_keywords',
|
|
|
|
'news_cat_meta_description' => 'seo_des',
|
|
|
|
],
|
|
|
|
'gl_product_keyword' => [
|
|
|
|
'tags_title' => 'seo_title',
|
|
|
|
'tags_meta_keywords' => 'seo_keywords',
|
|
|
|
'tags_meta_description' => 'seo_description',
|
|
|
|
'tags_content_title' => 'keyword_title',
|
|
|
|
'tags_content_description' => 'keyword_content',
|
|
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* topic-对相应字段
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $topic_fields = [
|
|
|
|
'gl_web_custom_template' => 'name',
|
|
|
|
'gl_product' => 'title',
|
|
|
|
'gl_product_category' => 'title',
|
|
|
|
'gl_blog' => 'name',
|
|
|
|
'gl_blog_category' => 'name',
|
|
|
|
'gl_news' => 'name',
|
|
|
|
'gl_news_category' => 'name',
|
|
|
|
'gl_product_keyword' => 'title'
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 使用核心关键词的key 数量
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
protected $core_keyword_keys = [
|
|
|
|
'page_meta_keywords' => 1,
|
|
|
|
'blog_cat_meta_keywords' => 8,
|
|
|
|
'news_cat_meta_keywords' => 8,
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function handle()
|
|
|
|
{
|
|
|
|
$task_id = $this->argument('task_id');
|
|
|
|
$task = ProjectUpdateTdk::where('status', ProjectUpdateTdk::STATUS_PENDING)->where('id', $task_id)->first();
|
|
|
|
if (!$task) {
|
|
|
|
echo date('Y-m-d H:i:s') . ' 任务不存在: ' . $task_id . PHP_EOL;
|
|
|
|
}
|
|
|
|
$project_id = $task->project_id;
|
|
|
|
|
|
|
|
echo date('Y-m-d H:i:s') . ' start project_id: ' . $project_id . PHP_EOL;
|
|
|
|
try {
|
|
|
|
$this->project = ProjectServer::useProject($project_id);
|
|
|
|
$this->seo_tdk($project_id, $task->id);
|
|
|
|
DB::disconnect('custom_mysql');
|
|
|
|
}catch (\Exception $e){
|
|
|
|
echo date('Y-m-d H:i:s') . 'line: '. $e->getLine() .' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
|
|
|
|
ProjectUpdateTdk::retry($task->id, $e->getMessage());
|
|
|
|
}
|
|
|
|
Cache::forget('project_deploy_optimize_info_' . $project_id);
|
|
|
|
echo date('Y-m-d H:i:s') . ' end project_id: ' . $project_id . PHP_EOL;
|
|
|
|
}
|
|
|
|
public function sendNotify($project_id, $route)
|
|
|
|
{
|
|
|
|
//获取当前项目的域名
|
|
|
|
$domainModel = new DomainInfo();
|
|
|
|
$domainInfo = $domainModel->read(['project_id'=>$project_id]);
|
|
|
|
if($domainInfo === false){
|
|
|
|
//获取测试域名
|
|
|
|
$deployBuildModel = new DeployBuild();
|
|
|
|
$buildInfo = $deployBuildModel->read(['project_id'=>$project_id]);
|
|
|
|
$this->param['domain'] = $buildInfo['test_domain'];
|
|
|
|
}else{
|
|
|
|
$this->param['domain'] = 'https://'.$domainInfo['domain'].'/';
|
|
|
|
}
|
|
|
|
$url = $this->param['domain'].'api/update_page/';
|
|
|
|
$param = [
|
|
|
|
'project_id' => $project_id,
|
|
|
|
'type' => 1,
|
|
|
|
'route' => $route,
|
|
|
|
'url' => [],
|
|
|
|
'language'=> [],
|
|
|
|
];
|
|
|
|
http_post($url, json_encode($param));
|
|
|
|
echo '更新中请稍后, 更新完成将会发送站内信通知更新结果!'. PHP_EOL;
|
|
|
|
}
|
|
|
|
public function seo_tdk($project_id, $task_id)
|
|
|
|
{
|
|
|
|
$notify_master = $notify_keyword = false;
|
|
|
|
//更新统计
|
|
|
|
$update = [];
|
|
|
|
$ai_commands = AiCommand::where('is_batch', 1)->select('key', 'scene', 'ai')->get()->toArray();
|
|
|
|
$ai_commands = Arr::setValueToKey($ai_commands, 'key');
|
|
|
|
foreach ($this->maps as $table => $map) {
|
|
|
|
$total_page = DB::connection('custom_mysql')->table($table)->count();
|
|
|
|
$update[$table] = ['total_page'=>$total_page, 'title'=>0, 'keyword'=>0, 'des'=>0,'keyword_title'=>0,'keyword_content'=>0];
|
|
|
|
echo date('Y-m-d H:i:s') . '更新--' . $table . ': 项目id' . $project_id . PHP_EOL;
|
|
|
|
$list = DB::connection('custom_mysql')->table($table)
|
|
|
|
->where(function ($query) use ($table, $map){
|
|
|
|
if($table == 'gl_product'){
|
|
|
|
foreach ($map as $field){
|
|
|
|
$field_arr = explode('.', $field);
|
|
|
|
$query->orWhereRaw('JSON_CONTAINS('.$field_arr[0].', "null", "$.'.$field_arr[1].'") OR JSON_EXTRACT('.$field_arr[0].', "$.'.$field_arr[1].'") = ""');
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
foreach ($map as $field){
|
|
|
|
$query->orWhereRaw($field . " IS NULL OR ".$field." = ''");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})->select('id')->get();
|
|
|
|
if (!empty($list)) {
|
|
|
|
$list = $list->toArray();
|
|
|
|
foreach ($list as $v) {
|
|
|
|
$v = (array)$v;
|
|
|
|
|
|
|
|
//缓存 在处理的项目数据 id
|
|
|
|
$cache_key = "seo_tdk_{$project_id}_{$table}_{$v['id']}";
|
|
|
|
if(!Redis::setnx($cache_key, 1)){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Redis::expire($cache_key, 120);
|
|
|
|
|
|
|
|
echo date('Y-m-d H:i:s') . '更新--' . $table . ': 项目id' . $project_id . ':id' . $v['id'] . PHP_EOL;
|
|
|
|
$v = DB::connection('custom_mysql')->table($table)->where('id', $v['id'])->first();
|
|
|
|
$v = (array)$v;
|
|
|
|
$data = [];
|
|
|
|
$json_field = '';
|
|
|
|
foreach ($map as $ai_key => $field) {
|
|
|
|
$field_arr = explode('.', $field);
|
|
|
|
if (count($field_arr) > 1) {
|
|
|
|
$json_field = $field_arr[0];
|
|
|
|
$value = json_decode($v[$field_arr[0]], true)[$field_arr[1]] ?? '';
|
|
|
|
} else {
|
|
|
|
$value = $v[$field] ?? '';
|
|
|
|
}
|
|
|
|
//已有值的 跳过
|
|
|
|
if ($value) {
|
|
|
|
echo $field.'已有值 跳过' . PHP_EOL;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
//AI生成
|
|
|
|
if (!empty($ai_commands[$ai_key]['ai'])) {
|
|
|
|
$prompt = $this->getPrompt($project_id, $ai_commands[$ai_key]['ai'], $table, $v);
|
|
|
|
if(!$prompt){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (count($field_arr) > 1) {
|
|
|
|
if($field_arr[1] == 'title'){
|
|
|
|
$update[$table]['title']++;
|
|
|
|
}elseif ($field_arr[1] == 'keyword'){
|
|
|
|
$update[$table]['keyword']++;
|
|
|
|
}elseif ($field_arr[1] == 'description'){
|
|
|
|
$update[$table]['des']++;
|
|
|
|
}
|
|
|
|
$data[$field_arr[0]][$field_arr[1]] = $this->ai_send($prompt);
|
|
|
|
}else{
|
|
|
|
if($field == 'title' || $field == 'seo_title'){
|
|
|
|
$update[$table]['title']++;
|
|
|
|
}
|
|
|
|
if($field == 'keywords' || $field == 'seo_keywords'){
|
|
|
|
$update[$table]['keyword']++;
|
|
|
|
}
|
|
|
|
if($field == 'seo_description' || $field == 'description' || $field == 'seo_des'){
|
|
|
|
$update[$table]['des']++;
|
|
|
|
}
|
|
|
|
if($field == 'keyword_title'){
|
|
|
|
$update[$table]['keyword_title']++;
|
|
|
|
}
|
|
|
|
if($field == 'keyword_content'){
|
|
|
|
$update[$table]['keyword_content']++;
|
|
|
|
}
|
|
|
|
$data[$field] = $this->ai_send($prompt);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//直接使用topic
|
|
|
|
if (count($field_arr) > 1) {
|
|
|
|
$data[$field_arr[0]][$field_arr[1]] = $v[$this->topic_fields[$table]] ?? '';
|
|
|
|
//使用核心关键词
|
|
|
|
if(in_array($ai_key, array_keys($this->core_keyword_keys))){
|
|
|
|
$data[$field_arr[0]][$field_arr[1]] = $this->mainKeywords($project_id, $this->core_keyword_keys[$ai_key]);
|
|
|
|
if(!empty($data[$field_arr[0]][$field_arr[1]])){
|
|
|
|
if($field_arr[1] == 'title'){
|
|
|
|
$data[$table]['title']++;
|
|
|
|
}elseif ($field_arr[1] == 'keyword'){
|
|
|
|
$data[$table]['keyword']++;
|
|
|
|
}elseif ($field_arr[1] == 'description'){
|
|
|
|
$data[$table]['des']++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
$data[$field] = $v[$this->topic_fields[$table]] ?? '';
|
|
|
|
//使用核心关键词
|
|
|
|
if(in_array($ai_key, array_keys($this->core_keyword_keys))){
|
|
|
|
$data[$field] = $this->mainKeywords($project_id, $this->core_keyword_keys[$ai_key]);
|
|
|
|
if(!empty($data[$field])){
|
|
|
|
if($field == 'title' || $field == 'seo_title'){
|
|
|
|
$update[$table]['title']++;
|
|
|
|
}
|
|
|
|
if($field == 'keywords' || $field == 'seo_keywords'){
|
|
|
|
$update[$table]['keyword']++;
|
|
|
|
}
|
|
|
|
if($field == 'seo_description' || $field == 'description' || $field == 'seo_des'){
|
|
|
|
$update[$table]['des']++;
|
|
|
|
}
|
|
|
|
if($field == 'keyword_title'){
|
|
|
|
$update[$table]['keyword_title']++;
|
|
|
|
}
|
|
|
|
if($field == 'keyword_content'){
|
|
|
|
$update[$table]['keyword_content']++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!$data){
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if($json_field){
|
|
|
|
$old_data = json_decode($v[$field_arr[0]], true);
|
|
|
|
foreach ($old_data ?: [] as $kk=>$vv){
|
|
|
|
empty($data[$json_field][$kk]) && $data[$json_field][$kk] = $vv;
|
|
|
|
}
|
|
|
|
$data[$json_field] = json_encode($data[$json_field]);
|
|
|
|
}
|
|
|
|
DB::connection('custom_mysql')->table($table)->where(['id' => $v['id']])->update($data);
|
|
|
|
if($table == 'gl_product_keyword'){
|
|
|
|
$notify_keyword = true;
|
|
|
|
}else{
|
|
|
|
$notify_master = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ProjectUpdateTdk::finish($task_id, $update);
|
|
|
|
|
|
|
|
//通知C端更新界面
|
|
|
|
$notify_master && $this->sendNotify($project_id, 1); //通知主站更新
|
|
|
|
$notify_keyword && $this->sendNotify($project_id, 4); //通知聚合页更新
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getPrompt($project_id, $prompt, $table, $data){
|
|
|
|
if(strpos($prompt, '{topic}') !== false){
|
|
|
|
$topic = $data[$this->topic_fields[$table]] ?? '';
|
|
|
|
if(!$topic){
|
|
|
|
echo 'topic为空 跳过' . PHP_EOL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$prompt = str_replace('{topic}', $topic, $prompt);
|
|
|
|
}
|
|
|
|
if(strpos($prompt, '{keyword}') !== false) {
|
|
|
|
$keyword = $this->mainKeywords($project_id, 1);
|
|
|
|
if(!$keyword){
|
|
|
|
echo '核心关键词为空 跳过' . PHP_EOL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$prompt = str_replace('{keyword}', $keyword, $prompt);
|
|
|
|
}
|
|
|
|
if(strpos($prompt, '{company name}') !== false) {
|
|
|
|
$company_name = $this->companyName($project_id);
|
|
|
|
if(!$company_name){
|
|
|
|
echo '公司英文全称为空 跳过' . PHP_EOL;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$prompt = str_replace('{company name}', $company_name, $prompt);
|
|
|
|
}
|
|
|
|
$prompt .= '.Please answer in ' . $this->getLang();
|
|
|
|
return $prompt;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function getDeployOptimize($project_id){
|
|
|
|
$cache_key = 'project_deploy_optimize_info_' . $project_id;
|
|
|
|
$info = Cache::get($cache_key);
|
|
|
|
if(!$info){
|
|
|
|
$projectOptimizeModel = new DeployOptimize();
|
|
|
|
$info = $projectOptimizeModel->read(['project_id' => $project_id], ['id', 'company_en_name', 'company_en_description', 'main_keywords']);
|
|
|
|
Cache::put($cache_key, $info, 600);
|
|
|
|
}
|
|
|
|
return $info;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @remark :获取公司英文名称
|
|
|
|
* @name :companyName
|
|
|
|
* @author :lyh
|
|
|
|
* @method :post
|
|
|
|
* @time :2023/10/30 11:22
|
|
|
|
*/
|
|
|
|
public function companyName($project_id, $key = '')
|
|
|
|
{
|
|
|
|
$data = [
|
|
|
|
'product_long_description',
|
|
|
|
];
|
|
|
|
$info = $this->getDeployOptimize($project_id);
|
|
|
|
if (in_array($key, $data)) {
|
|
|
|
return $info['company_en_description'];
|
|
|
|
} else {
|
|
|
|
return $info['company_en_name'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @remark :获取公司核心关键词
|
|
|
|
* @name :mainKeywords
|
|
|
|
* @author :lyh
|
|
|
|
* @method :post
|
|
|
|
* @time :2023/10/30 11:22
|
|
|
|
*/
|
|
|
|
public function mainKeywords($project_id, $num)
|
|
|
|
{
|
|
|
|
$str = '';
|
|
|
|
$info = $this->getDeployOptimize($project_id);
|
|
|
|
if (!empty($info['main_keywords'])) {
|
|
|
|
$main_keywords = explode("\r\n", $info['main_keywords']);
|
|
|
|
//随机取
|
|
|
|
shuffle($main_keywords);
|
|
|
|
$main_keywords = array_slice($main_keywords, 0, $num);
|
|
|
|
$str = implode(", ", $main_keywords);
|
|
|
|
}
|
|
|
|
return $str;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getLang(){
|
|
|
|
$lang = WebLanguage::getLangById($this->project['main_lang_id']??1);
|
|
|
|
return $lang['english'] ?? 'English';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @remark :AI发送
|
|
|
|
* @name :ai_send
|
|
|
|
* @author :lyh
|
|
|
|
* @method :post
|
|
|
|
* @time :2023/8/19 10:40
|
|
|
|
*/
|
|
|
|
public function ai_send($prompt)
|
|
|
|
{
|
|
|
|
$text = Gpt::instance()->openai_chat_qqs($prompt);
|
|
|
|
$text = Common::deal_keywords($text);
|
|
|
|
$text = Common::deal_str($text);
|
|
|
|
|
|
|
|
//包含这写字 重新生成
|
|
|
|
if(Str::contains(Str::lower($text), ['[your brand]', '[brand name]'])){
|
|
|
|
return $this->ai_send($prompt);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @remark :发送站内信
|
|
|
|
* @name :send_message
|
|
|
|
* @author :lyh
|
|
|
|
* @method :post
|
|
|
|
* @time :2023/11/4 10:22
|
|
|
|
*/
|
|
|
|
public function send_message($project_id){
|
|
|
|
$user = new User();
|
|
|
|
$userInfo = $user->read(['project_id'=>$project_id,'role_id'=>0]);
|
|
|
|
$data["title"] = "seo更新通知---完成";
|
|
|
|
$data["user_list"] = $userInfo['id'];
|
|
|
|
$data["content"] = "seo更新成功,更新完成时间".date('Y-m-d H:i:s');
|
|
|
|
$mail = new Mail();
|
|
|
|
return $mail->add($data);
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|