|
|
|
<?php
|
|
|
|
|
|
|
|
namespace App\Console\Commands\Tdk;
|
|
|
|
|
|
|
|
use App\Exceptions\ValidateException;
|
|
|
|
use App\Helper\Arr;
|
|
|
|
use App\Helper\Gpt;
|
|
|
|
use App\Models\Ai\AiCommand;
|
|
|
|
use App\Models\Com\NoticeLog;
|
|
|
|
use App\Models\Domain\DomainInfo;
|
|
|
|
use App\Models\Product\Keyword;
|
|
|
|
use App\Models\Project\AggregateKeywordAffix;
|
|
|
|
use App\Models\Project\DeployBuild;
|
|
|
|
use App\Models\Project\DeployOptimize;
|
|
|
|
use App\Models\Project\ProjectKeywordAiTask;
|
|
|
|
use App\Services\ProjectServer;
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 关键词聚合页AI生成内容
|
|
|
|
* Class InitProject
|
|
|
|
* @package App\Console\Commands
|
|
|
|
* @author zbj
|
|
|
|
* @date 2025/06/06
|
|
|
|
*/
|
|
|
|
class KeywordPageAiContent extends Command
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The name and signature of the console command.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $signature = 'keyword_page_ai_content';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The console command description.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $description = '关键词聚合页AI生成内容';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 统计图表类型 随机一个
|
|
|
|
* @var string[]
|
|
|
|
*/
|
|
|
|
protected $chart_types = [
|
|
|
|
'柱状图',
|
|
|
|
'折线图',
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function handle()
|
|
|
|
{
|
|
|
|
while (true) {
|
|
|
|
$task = ProjectKeywordAiTask::getPendingTask();
|
|
|
|
if (!$task) {
|
|
|
|
sleep(10);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$project_id = $task->project_id;
|
|
|
|
|
|
|
|
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' start project_id: ' . $project_id . PHP_EOL;
|
|
|
|
try {
|
|
|
|
ProjectServer::useProject($project_id);
|
|
|
|
|
|
|
|
$update_rows = $this->ai_content($task);
|
|
|
|
|
|
|
|
DB::disconnect('custom_mysql');
|
|
|
|
|
|
|
|
ProjectKeywordAiTask::finish($task->id, $update_rows);
|
|
|
|
|
|
|
|
// $update_rows && $this->sendNotify($project_id);
|
|
|
|
|
|
|
|
} catch (ValidateException $e) {
|
|
|
|
echo getmypid() . ' ' . date('Y-m-d H:i:s') . 'line: ' . $e->getLine() . ' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
|
|
|
|
$task->status = ProjectKeywordAiTask::STATUS_FAIL;
|
|
|
|
$task->remark = mb_substr($e->getMessage(), 0, 250);
|
|
|
|
$task->save();
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
echo getmypid() . ' ' . date('Y-m-d H:i:s') . 'line: ' . $e->getLine() . ' error: ' . $project_id . '->' . $e->getMessage() . PHP_EOL;
|
|
|
|
ProjectKeywordAiTask::retry($task->id, $e->getMessage());
|
|
|
|
}
|
|
|
|
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' end project_id: ' . $project_id . PHP_EOL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ProjectKeywordAiTask $task
|
|
|
|
* @author zbj
|
|
|
|
* @date 2025/6/6
|
|
|
|
*/
|
|
|
|
public function ai_content(ProjectKeywordAiTask $task)
|
|
|
|
{
|
|
|
|
//前后缀
|
|
|
|
$affix = AggregateKeywordAffix::where('project_id', $task->project_id)->first();
|
|
|
|
$prefix = empty($affix['prefix']) ? [] : explode("\r\n", $affix['prefix']);
|
|
|
|
$suffix = empty($affix['suffix']) ? [] : explode("\r\n", $affix['suffix']);
|
|
|
|
if (!$prefix || !$suffix) {
|
|
|
|
throw new ValidateException('扩展标题前后缀不存在');
|
|
|
|
}
|
|
|
|
//公司英文描述
|
|
|
|
$company_en_description = DeployOptimize::where('project_id', $task->project_id)->value('company_en_description');
|
|
|
|
if (!$company_en_description) {
|
|
|
|
throw new ValidateException('公司英文描述不存在');
|
|
|
|
}
|
|
|
|
//指令
|
|
|
|
$ai_commands = AiCommand::whereIn('key', ['tag_sale_content', 'tag_count_content', 'tag_data_table'])->where('project_id', 0)->select('key', 'scene', 'ai')->get()->toArray();
|
|
|
|
$project_ai_commands = AiCommand::whereIn('key', ['tag_sale_content', 'tag_count_content', 'tag_data_table'])->where('project_id', $task->project_id)->select('key', 'scene', 'ai')->get()->toArray();
|
|
|
|
$ai_commands = Arr::setValueToKey($ai_commands, 'key');
|
|
|
|
$project_ai_commands = Arr::setValueToKey($project_ai_commands, 'key');
|
|
|
|
foreach ($ai_commands as $k => $ai_command) {
|
|
|
|
if (!empty($project_ai_commands[$k])) {
|
|
|
|
$ai_commands[$k] = $project_ai_commands[$k];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//没有标题、文案、图表的关键词
|
|
|
|
$keyword_ids = Keyword::whereNull('sale_title')->orWhereNull('sale_content')->orWhereNull('table_html')
|
|
|
|
->orWhereNull('count_title')->orWhereNull('count_html')
|
|
|
|
->pluck('id')
|
|
|
|
->toArray();
|
|
|
|
$update_rows = 0;
|
|
|
|
foreach ($keyword_ids as $id) {
|
|
|
|
//缓存 在处理的项目数据 id
|
|
|
|
$cache_key = "keyword_page_ai_content_task_lock_{$task->project_id}_{$id}";
|
|
|
|
if (!Redis::setnx($cache_key, 1)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Redis::expire($cache_key, 120);
|
|
|
|
|
|
|
|
$keyword = Keyword::where('id', $id)->select(['id', 'title', 'sale_title', 'sale_content', 'table_html', 'count_title', 'count_html'])->first();
|
|
|
|
|
|
|
|
echo getmypid() . ' ' . date('Y-m-d H:i:s') . ' id:' . $keyword['id'] . ' project_id:' . $task->project_id . PHP_EOL;
|
|
|
|
|
|
|
|
$update = false;
|
|
|
|
if (empty($keyword['sale_title'])) {
|
|
|
|
$sale_title = $this->new_title($keyword['title'], $prefix, $suffix);
|
|
|
|
$keyword->sale_title = $sale_title;
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
if (empty($keyword['sale_content']) && $keyword->sale_title) {
|
|
|
|
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_sale_content']['ai']);
|
|
|
|
if ($content) {
|
|
|
|
$keyword->sale_content = $content;
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($keyword['table_html']) && $keyword->sale_title) {
|
|
|
|
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_data_table']['ai']);
|
|
|
|
if ($content) {
|
|
|
|
$keyword->table_html = str_replace(['```html', '``html', '```'], '', $content);
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($keyword['count_title'])) {
|
|
|
|
$count_title = $this->new_title($keyword['title'], $prefix, $suffix);
|
|
|
|
$count_title && $keyword->count_title = $count_title;
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
if (empty($keyword['count_html']) && $keyword->sale_title) {
|
|
|
|
$content = $this->ai_send($keyword->sale_title, $company_en_description, $ai_commands['tag_count_content']['ai']);
|
|
|
|
if ($content) {
|
|
|
|
$keyword->count_html = $this->fixChart(str_replace(['```html', '``html', '```'], '', $content));
|
|
|
|
$update = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($update) {
|
|
|
|
$keyword->save();
|
|
|
|
$update_rows++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $update_rows;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function new_title($title, $prefix, $suffix)
|
|
|
|
{
|
|
|
|
//打乱顺序
|
|
|
|
shuffle($prefix);
|
|
|
|
shuffle($suffix);
|
|
|
|
//标题(title):{聚合页扩展标题前缀} keywords {聚合页扩展标题后缀} {聚合页扩展标题后缀}
|
|
|
|
|
|
|
|
return sprintf('%s %s %s %s', $prefix[0], $title, $suffix[0], $suffix[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function ai_send($title, $company_description, $prompt)
|
|
|
|
{
|
|
|
|
if (strpos($prompt, '{title}') !== false) {
|
|
|
|
$prompt = str_replace('{title}', $title, $prompt);
|
|
|
|
}
|
|
|
|
if (strpos($prompt, '{company introduction}') !== false) {
|
|
|
|
$prompt = str_replace('{company introduction}', $company_description, $prompt);
|
|
|
|
}
|
|
|
|
if (strpos($prompt, '{chart_type}') !== false) {
|
|
|
|
shuffle($this->chart_types);
|
|
|
|
$prompt = str_replace('{chart_type}', $this->chart_types[0], $prompt);
|
|
|
|
}
|
|
|
|
|
|
|
|
$text = Gpt::instance()->openai_chat_qqs($prompt);
|
|
|
|
if (!$text) {
|
|
|
|
echo getmypid() . ' ' . '生成失败' . PHP_EOL;
|
|
|
|
}
|
|
|
|
return $text;
|
|
|
|
}
|
|
|
|
|
|
|
|
function fixChart($html)
|
|
|
|
{
|
|
|
|
$html = '<body>' . $html . '</body>';
|
|
|
|
$dom = new \DOMDocument();
|
|
|
|
@$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8'));
|
|
|
|
$canvas_count = $dom->getElementsByTagName('canvas')->count();
|
|
|
|
//没有canvas
|
|
|
|
if (!$canvas_count) {
|
|
|
|
$div = $dom->getElementsByTagName('div');
|
|
|
|
foreach ($div as $element) {
|
|
|
|
if ($element->hasAttribute('id')) {
|
|
|
|
$canvas = $dom->createElement('canvas');
|
|
|
|
$canvas->setAttribute('id', $element->getAttribute('id'));
|
|
|
|
$element->removeAttribute('id');
|
|
|
|
$element->appendChild($canvas);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$body = $dom->getElementsByTagName('body')->item(0);
|
|
|
|
$modifiedHtml = '';
|
|
|
|
foreach ($body->childNodes as $child) {
|
|
|
|
$modifiedHtml .= $dom->saveHTML($child);
|
|
|
|
}
|
|
|
|
return $modifiedHtml;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function sendNotify($project_id)
|
|
|
|
{
|
|
|
|
//获取当前项目的域名
|
|
|
|
$domainModel = new DomainInfo();
|
|
|
|
$domainInfo = $domainModel->read(['project_id' => $project_id]);
|
|
|
|
if ($domainInfo === false) {
|
|
|
|
//获取测试域名
|
|
|
|
$deployBuildModel = new DeployBuild();
|
|
|
|
$buildInfo = $deployBuildModel->read(['project_id' => $project_id]);
|
|
|
|
$domain = $buildInfo['test_domain'];
|
|
|
|
} else {
|
|
|
|
$domain = 'https://' . $domainInfo['domain'] . '/';
|
|
|
|
}
|
|
|
|
$url = $domain . 'api/update_page/';
|
|
|
|
$param = [
|
|
|
|
'project_id' => $project_id,
|
|
|
|
'type' => 1,
|
|
|
|
'route' => 2,
|
|
|
|
'url' => [],
|
|
|
|
'language' => [],
|
|
|
|
];
|
|
|
|
NoticeLog::createLog(NoticeLog::GENERATE_PAGE, json_encode(['c_url' => $url, 'c_params' => $param]), date('Y-m-d H:i:s', time() + 300));
|
|
|
|
echo getmypid() . ' ' . '更新中请稍后, 更新完成将会发送站内信通知更新结果!' . PHP_EOL;
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|