Browse Source

feat(queue): 新增爆文复刻队列处理功能

- 创建 ExpiosiveReplica 队列消费者类,实现文章内容抓取与AI复刻逻辑
- 实现从URL抓取HTML并提取标题和内容的功能
- 添加配图获取及AI指令整合逻辑
- 更新 CreationTask 队列中配图分类查询方式,直接使用任务中的分类ID
- 修改 ExpiosiveReplica 模型,增加 ai_time 字段的格式化方法
- 在 ExpiosiveReplicaService 中投递队列消息,并优化列表查询字段
master
zhangf@suq.cn 5 days ago
parent
commit
ff84116bbb
  1. 4
      app/model/ExpiosiveReplica.php
  2. 7
      app/queue/redis/CreationTask.php
  3. 222
      app/queue/redis/ExpiosiveReplica.php
  4. 4
      app/service/ExpiosiveReplicaService.php

4
app/model/ExpiosiveReplica.php

@ -30,4 +30,8 @@ class ExpiosiveReplica extends BaseModel
*/
public $timestamps = true;
public function getAiTimeAttr($attr)
{
return $attr == 0 ? '未完成' : date('Y-m-d H:i:s', $attr);
}
}

7
app/queue/redis/CreationTask.php

@ -6,7 +6,6 @@ use app\dao\CreationArticleDao;
use app\dao\CreationTaskDao;
use app\dao\DistillationQuestionsDao;
use app\dao\DistillationWordDao;
use app\dao\EnterprisePortraitCategoryDao;
use app\dao\EnterprisePortraitLibraryDao;
use plugin\piadmin\app\utils\openai\OpenAiClient;
use Webman\RedisQueue\Consumer;
@ -156,10 +155,10 @@ class CreationTask implements Consumer
private function getPortraitLibrary($task)
{
echo "开始获取配图\n";
$categoryDao = app()->make(EnterprisePortraitCategoryDao::class);
$category = $categoryDao->getOne(['id' => $task['portrait_category_id']]);
// $categoryDao = app()->make(EnterprisePortraitCategoryDao::class);
// $category = $categoryDao->getOne(['id' => $task['portrait_category_id']]);
$libraryDao = app()->make(EnterprisePortraitLibraryDao::class);
$library = $libraryDao->getColumn(['category_id' => $category['id']], 'url');
$library = $libraryDao->getColumn(['category_id' => $task['portrait_category_id']], 'url');
if (!$library) {
return [
'images' => [],

222
app/queue/redis/ExpiosiveReplica.php

@ -0,0 +1,222 @@
<?php
namespace app\queue\redis;
use app\dao\AiCommandDao;
use app\dao\EnterprisePortraitLibraryDao;
use app\dao\ExpiosiveReplicaDao;
use plugin\piadmin\app\utils\openai\OpenAiClient;
use Webman\RedisQueue\Consumer;
class ExpiosiveReplica implements Consumer
{
// 要消费的队列名
public $queue = 'expiosive_replica';
// 连接名,对应 plugin/webman/redis-queue/redis.php 里的连接`
public $connection = 'default';
//抓取文章提示词
private $fetchWebPrompt = '- Role: HTML内容解析专家和信息提取工程师
- Background: 用户需要从提供的HTML代码中精确提取文章标题和内容。这表明用户已经获取了网页的HTML源代码,需要从中提取特定的信息,以便进行进一步的处理或分析。
- Profile: 你是一位在HTML内容解析和信息提取领域有着丰富经验的专家,能够精准地从HTML代码中提取所需的信息。你熟悉HTML结构和数据提取技术,能够高效地完成任务。
- Skills: 你具备HTML解析能力、数据提取技术、信息筛选技巧以及对网页结构的深刻理解。能够根据用户提供的HTML代码,准确提取文章标题和内容。
- Goals: 根据用户提供的HTML代码,精确提取文章标题和内容,确保提取的信息准确无误。
- Constrains: 提取的内容必须严格来自用户提供的HTML代码,确保信息的真实性和完整性。避免提取无关内容,确保提取的文章标题和内容清晰可读。
- OutputFormat: 输出格式为JSON格式,将提取的文章标题放在`title`字段中,内容放在`content`字段中。
- Workflow:
1. 接收用户提供的HTML代码。
2. 解析HTML代码,定位文章标题和内容的HTML元素。
3. 提取文章标题和内容,并进行格式化处理,确保信息清晰可读。
4. 将提取的信息整合到JSON格式中,包括`title``content`字段。
- Examples:
- 假设用户提供的HTML代码如下:
```html
<html>
<head>
<title>2025年科技行业发展趋势</title>
</head>
<body>
<h1>2025年科技行业发展趋势</h1>
<p>随着科技的飞速发展,2025年科技行业呈现出许多新的趋势。</p>
<p>人工智能在医疗、教育和交通等领域的应用越来越广泛。</p>
<p>5G技术的普及也推动了物联网的发展。</p>
<p>此外,量子计算和区块链技术也在逐步走向商业化应用。</p>
</body>
</html>
```
提取结果如下:
```json
{
"title": "2025年科技行业发展趋势",
"content": "随着科技的飞速发展,2025年科技行业呈现出许多新的趋势。人工智能在医疗、教育和交通等领域的应用越来越广泛。5G技术的普及也推动了物联网的发展。此外,量子计算和区块链技术也在逐步走向商业化应用。"
}';
//爆文复刻用提示词
private $defaultPrompt = '- Role: 爆文复刻专家、内容优化顾问及视觉内容整合师
- Background: 用户需要对提供的标题和文章进行复刻,以满足特定的要求。用户会提供一些配图,并指定在文章合适的位置插入这些图片。这意味着用户希望在保留原文核心内容和风格的基础上,对文章进行优化和调整,使其更接近“爆文”的标准,同时通过视觉元素增强文章的吸引力和可读性。
- Profile: 你是一位在爆文创作、内容优化和视觉内容整合领域有着丰富经验的专家,能够精准地把握爆文的特点和用户的需求,对文章进行高质量的复刻。你熟悉各种写作风格和内容结构,能够根据用户的要求,对文章进行有效的调整和优化,同时保留原文的核心价值和风格。
- Skills: 你具备爆文创作能力、内容优化技巧、语言表达能力、视觉内容整合能力以及对用户需求的敏锐洞察力。能够根据用户提供的标题、文章和配图,结合复刻要求,对文章进行精准的调整和优化,并合理插入配图。
- Goals: 根据用户提供的标题、文章和配图,按照用户的要求进行复刻,确保复刻后的文章更接近“爆文”的标准,同时保留原文的核心内容和风格,并在合适的位置插入配图,增强文章的视觉效果和可读性。
- Constrains: 复刻后的文章必须严格遵循用户的要求,包括内容调整、风格优化、语言润色等。确保复刻后的文章在满足要求的同时,保持原有的核心信息和风格特点。配图插入位置应合理,与文章内容紧密相关,增强文章的可读性和视觉效果。
- OutputFormat: 输出格式为完整的文章正文,包括复刻后的标题和内容。如果用户有特定的格式要求,应严格按照要求输出。配图应插入到文章的合适位置,图片数量符合用户指定的要求。
- Workflow:
1. 接收用户提供的标题、文章和配图。
2. 仔细分析用户提出的复刻要求,明确复刻的方向和重点。
3. 根据复刻要求,对文章的标题和内容进行优化和调整,提升文章的吸引力和可读性。
4. 确定配图的插入位置,确保图片与文章内容紧密相关,增强视觉效果。
5. 将配图合理插入到文章的合适位置,完成复刻。
6. 审核复刻后的文章,确保内容符合用户的要求,语言流畅且逻辑清晰,用户提出的要求仅针对提供的标题和内容,不针对复刻后的标题和内容。
- Examples:
- 假设用户提供的标题是“如何提高工作效率”,文章内容如下:
```
在当今快节奏的工作环境中,提高工作效率是每个职场人士都关心的问题。以下是一些实用的建议:
- 制定清晰的工作计划。
- 优先处理重要任务。
- 避免不必要的干扰。
- 定期休息,保持精力充沛。
```
用户提供了两张配图,并要求在合适的位置插入:
```json
{
"title": "工作效率翻倍的秘密:职场高手的4个绝招",
"content": "在快节奏的职场竞争中,工作效率就是生命线!今天,就来揭秘那些职场高手如何轻松搞定工作,效率翻倍的秘密。![配图1](配图地址1)\n\n首先,高手们都会制定一份超清晰的工作计划,每一步都精准无误。其次,他们总是优先搞定那些最重要的任务,绝不拖泥带水。![配图2](配图地址2)\n\n再来,他们懂得如何屏蔽那些无意义的干扰,专注就是他们的超能力。最后,别小看定期休息,这可是保持精力充沛的不二法门。赶紧试试这些绝招,让你的工作效率瞬间提升!"
}
我提供的JSON数据:配图(images)。创作要求(prompt),仅针对提供的内容(content),不针对创作的文章,尤其是不影响配图的插入。标题(title),内容(contnet)。数据:';
// 消费
public function consume($data)
{
echo "开始消费爆发复刻,数据ID为:" . $data['replica_id'] . "\n";
//获取复刻任务
$replicaDao = app()->make(ExpiosiveReplicaDao::class);
//状态改为复刻
$replicaDao->update($data['replica_id'], ['status' => 2]);
//获取复刻任务明细
$replicaTask = $replicaDao->get($data['replica_id']);
//解析url获取文章内容
$html = $this->fetchArticleContent($replicaTask['url']);
$fetch_prompt = $this->fetchWebPrompt . ',需要提取的html为:' . $html;
$result = OpenAiClient::chat($fetch_prompt);
$title = $result['content']['title'];
$content = $result['content']['content'];
//拼接提示词
$prompts = array_merge($this->getPortraitLibrary($replicaTask),$this->getAiCommand($replicaTask));
$prompts['title'] = $title;
$prompts['content'] = $content;
$finalPrompt = $this->defaultPrompt . '\n' . json_encode($prompts, JSON_UNESCAPED_UNICODE);
// echo "最终提示词:\n";
// var_export($finalPrompt);
$airesult = OpenAiClient::chat($finalPrompt);
$updata = [
'url_web' => $html,
'title' => $result['content']['title'],
'content' => $result['content']['content'],
'ai_title' => $airesult['content']['title'],
'ai_content' => $airesult['content']['content'],
'status' => 3,
'ai_time' => time()
];
var_export($updata);
//更改任务状态为完成
$replicaDao->update($data['replica_id'], $updata);
var_export('任务完成');
}
public function onConsumeFailure(\Throwable $e, $package)
{
echo "执行失败\n";
echo $e->getMessage() . "\n";
echo $e->getFile() . "\n";
echo $e->getLine() . "\n";
// 无需反序列化
var_export($package);
//记录失败
$replicaDao = app()->make(ExpiosiveReplicaDao::class);
$replicaDao->update($package['data']['replica_id'], [
'status' => 4,
'try_count' => $package['attempts'],
'status_msg' => $package['error']
]);
}
/**
* 获取配图及使用数量
*/
private function getPortraitLibrary($replicaTask)
{
echo "开始获取配图\n";
$libraryDao = app()->make(EnterprisePortraitLibraryDao::class);
$library = $libraryDao->getColumn(['category_id' => $replicaTask['portrait_category_id']], 'url');
echo "获取到配图:" . count($library) . "\n";
var_export($library);
if (!$library) {
return [
'images' => [],
];
}
return [
'images' => $library,
];
}
/**
* 获取创作指令
* type = 3 流量复刻
*/
private function getAiCommand($replicaTask)
{
echo "开始获取指令:" . 3 . "\n";
$aiCommandDao = app()->make(AiCommandDao::class);
$ai_command_id = $replicaTask['ai_command_id'];
$command = $aiCommandDao->getOne([
'type' => 3,
'id' => $ai_command_id
]);
echo "获取到指令:" . $command['content'] . "\n";
if (!$command) {
return [
'prompt' => ''
];
}
return [
'prompt' => $command['content']
];
}
/**
* 从URL抓取文章内容
* @param string $url
* @return string
*/
public function fetchArticleContent(string $url): string
{
// 使用 cURL 获取网页内容
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$html = curl_exec($ch);
curl_close($ch);
// 移除script标签及其内容
$html = preg_replace('/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi', '', $html);
// 移除style标签及其内容
$html = preg_replace('/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/mi', '', $html);
// 移除link标签(通常用于引入CSS)
$html = preg_replace('/<link\b[^>]*>/mi', '', $html);
// 移除meta标签
$html = preg_replace('/<meta\b[^>]*>/mi', '', $html);
// 移除所有标签的style属性(行内样式)
$html = preg_replace('/(<[^>]+) style=".*?"/i', '$1', $html);
// 移除所有标签的class属性
$html = preg_replace('/(<[^>]+) class=".*?"/i', '$1', $html);
// 移除所有标签的id属性
$html = preg_replace('/(<[^>]+) id=".*?"/i', '$1', $html);
// 移除多余的空白行
$html = preg_replace('/^\s*[\r\n]/m', '', $html);
$html = str_replace(' ', '', $html);
return $html;
}
}

4
app/service/ExpiosiveReplicaService.php

@ -7,6 +7,7 @@ use plugin\piadmin\app\base\BaseService;
use plugin\piadmin\app\exception\ApiException;
use plugin\piadmin\app\utils\RequestUtils;
use support\think\Db;
use Webman\RedisQueue\Redis;
class ExpiosiveReplicaService extends BaseService
{
@ -31,6 +32,7 @@ class ExpiosiveReplicaService extends BaseService
$data = $this->dao->save($params);
Db::commit();
// 投递消息
Redis::send('expiosive_replica', ['replica_id' => $data['id']]);
} catch (\Exception $exception) {
Db::rollback();
throw new ApiException($exception->getMessage());
@ -57,7 +59,7 @@ class ExpiosiveReplicaService extends BaseService
if (isNotBlank($params['end_time'])) {
$query[] = ['create_time', '<=', strtotime($params['end_time'] . ' 23:59:59')];
}
$list = $this->dao->getList($query, '*', $page, $limit, "$sortField $sortRule");
$list = $this->dao->getList($query, 'id,name,url,title,ai_time', $page, $limit, "$sortField $sortRule");
$count = $this->dao->getCount($query);
return compact('list', 'count');
}

Loading…
Cancel
Save