切换OPENAI

This commit is contained in:
chengxl
2025-07-17 16:49:39 +08:00
parent 57da2ea0d6
commit d1a97400fa

View File

@@ -2,17 +2,16 @@
namespace app\common;
use think\Cache;
use think\Db;
use think\Queue;
class OpenAi
{
protected $sApiKey = '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';
protected $proxy = '';
protected $sUrl = 'https://api.openai.com/v1/chat/completions';
protected $sUrl = 'http://chat.taimed.cn/v1/chat/completions';//'https://api.openai.com/v1/chat/completions';
protected $curl;
protected $sResponesData;
protected $sError;
protected $timeout = 60;
protected $timeout = 300;
//JAVA接口
protected $sJavaUrl = "http://ts.tmrjournals.com/";
//官网文件地址
@@ -21,7 +20,6 @@ class OpenAi
protected $sTmrUrl = "http://journalapi.tmrjournals.com/public/index.php";//"http://zmzm.journal.dev.com/";//;
protected $aArticleImportantPrompt = [
"journal_scope" => [
'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
"journal_scope": {
@@ -32,7 +30,6 @@ class OpenAi
"criteria" => "根据文章的标题:{title};摘要:{abstrart}以及期刊范围:{scope}来判断文章是否符合目标期刊{journal_name}"
],
"attribute" => [
'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
"attribute": {
@@ -41,90 +38,144 @@ class OpenAi
}
}',
"criteria" => "请结合以下几点【研究内容的原创性论文中的研究内容是否与已有的研究重复是否在同样的领域提出了类似的结论但在方法或结果上有所创新如果有作者是否清楚地解释了如何与之前的研究不同或者如何在原有基础上进行扩展或改进如果是综述文章汇总并综合最新的研究成果尤其是近几年内的重要发现展示领域内最新的进展成果。作者可以识别出未被充分讨论的问题或提出新的研究问题而不是简单文献堆砌。文章中的图表创新能否将信息的清晰呈现方便读者理解复杂研究问题。论文方法创新性评估要点是否采用了新的实验模型或创新的实验设计能有效解决当前研究中的难点或空白是否有合理的对照组和多组实验设计确保研究结果的可靠性是否使用了当前前沿的技术如高通量测序、CRISPR基因编辑等提高了实验精度或数据分析能力是否结合了跨学科的方法如生物信息学、人工智能等是否应用了多种验证手段或统计方法确保结果的可信度是否通过细胞实验、动物模型等多重验证确保实验结果的可靠性结论与数据的创新性研究结论是否提出了新观点或新见解是否提供了新的实验数据或观察结果能够突破当前的研究局限例如发现了新的生物标志物或对已知生物通路的作用机制提供了全新的解释】评估文章内容{content}是否有科学前沿性和创新性?"
],
"contradiction" => [
'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
"contradiction": {
"assessment": "是/否",
"explanation": "请引用具体段落说明矛盾之处"
}
}',
"criteria" => "根据文章内容{content}分析是否前后矛盾或存在逻辑不一致的问题?"
],
"unreasonable" => [
'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
"unreasonable": {
"assessment": "是/否",
"explanation": "包括实验设计、数据分析、结论推导等方面的问题"
}
}',
"criteria" => "根据文章内容{content}分析是否有明显的不合理之处?"
]
//,
// "contradiction" => [
// 'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
// 请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
// "contradiction": {
// "assessment": "是/否",
// "explanation": "请引用具体段落说明矛盾之处"
// }
// }',
// "criteria" => "根据文章内容{content}分析是否前后矛盾或存在逻辑不一致的问题?"
// ],
// "unreasonable" => [
// 'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。
// 请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构{
// "unreasonable": {
// "assessment": "是/否",
// "explanation": "包括实验设计、数据分析、结论推导等方面的问题"
// }
// }',
// "criteria" => "根据文章内容{content}分析是否有明显的不合理之处?"
// ]
];
//定义redis连接
private $redis;
public function __construct()
{
$config = \think\Config::get('queue');
$this->redis = new \Redis();
$this->redis->connect($config['host'], $config['port']);
if (!empty($config['password'])) {
$this->redis->auth($config['password']);
}
$this->redis->select($config['select']);
// 初始化 Redis 连接
// $this->redis = Cache::store('redis')->handler();
}
/**
* 构建公微模版-处理提示词【默认】
*/
public function buildDefaultPrompt($aSearch = [])
{
public function buildDefaultPrompt($aSearch = []){
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化输出[中文]【必须严格遵循以下 JSON 结构
{
"covered": "【列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析】",
"digest": "【学术规范翻译并提炼摘要强调逻辑性、科学术语准确性和表达严谨性采用段落形式总字数不超过500字】",
"research_background": "【提炼研究背景采用连贯的段落形式总字数超过200字】",
"discussion_results": "【针对文章简单总结讨论和结果采用连贯的段落形式总字数超过450字】",
"research_method": "【总结文章的研究方法采用连贯的段落形式总字数超过300字】",
"prospect": "【针对稿件内容进行展望撰写,采用连贯的段落形式】",
"highlights": "【总结归纳亮点至少3点每点用分号分隔】",
"title_chinese": "【将标题翻译成中文:内容需自然流畅、口语化、连贯性、学术性】",
"content": "【将内容翻译成中文,需自然流畅、口语化、连贯性、学术性,保留原文的章节结构和图表编号】"
}';
你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,请返回中文解释!返回格式必须严格遵循以下JSON结构';
$aQuestion = [
"covered" => "[列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析]",
"digest" => "[学术规范翻译并提炼摘要强调逻辑性、科学术语准确性和表达严谨性采用段落形式总字数不超过500字]",
"research_background" => "[提炼研究背景采用连贯的段落形式总字数超过200字]",
"discussion_results" => "[针对文章简单总结讨论和结果采用连贯的段落形式总字数超过450字]",
"research_method" => "[总结文章的研究方法采用连贯的段落形式总字数超过300字]",
"prospect" => "[针对稿件内容进行展望撰写,采用连贯的段落形式]",
"highlights" => "[总结归纳亮点至少3点每点用分号分隔]",
"title_chinese" => "[将标题翻译成中文:内容需自然流畅、口语化、连贯性、学术性]"
// ,
// "content" => "将内容翻译成中文,需自然流畅、口语化、连贯性、学术性,保留原文的章节结构和图表编号"
];
//问题处理
$aMessage = [];
foreach($aQuestion as $key => $value){
//修改当前内容
$sInfo = json_encode([$key => $value],JSON_UNESCAPED_UNICODE);
$sSysMessagePromptInfo = $sSysMessagePrompt.$sInfo;
if($key == "title_chinese"){
$sUserPrompt = '{#title_chinese#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
}
if($key == "content"){
$sUserPrompt = '{#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
}
if(!in_array($key,["title_chinese","content"])){
$sUserPrompt = '标题:{#title_chinese#} 摘要: {#abstract#} 内容: {#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
}
$aMessage[] = [
['role' => 'system', 'content' => $sSysMessagePromptInfo],
['role' => 'user', 'content' => $sUserPrompt]
];
}
return $aMessage;
}
/**
* 构建公微模版-处理提示词【Review】
*/
public function buildReviewPrompt($aSearch = [])
{
public function buildReviewPrompt($aSearch = []){
if(empty($aSearch)){
return [];
}
$sSysMessagePrompt = '
你是一位专业的医学学术翻译与传播专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化[中文]输出【必须严格遵循以下 JSON 结构】:
{
"covered": "【列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析】",
"overview": "【按照学术规范翻译并提炼文章概述整体内容应大于1200字其中应包含文章背景不少于400字其他内容提炼更强调逻辑性、科学术语准确性和表达的严谨性注意内容不要有严重重复采用连贯的段落形式】",
"summary": "【针对文章结论生成一个简单总结内容不要和文章概述重复字数150以内】",
"title_chinese": "【将标题翻译成中文需自然流畅、口语化、连贯性、学术性】",
"content": "【将文章内容翻译成中文,需自然流畅、口语化、连贯性、学术性】"
}';
你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化输出[中文]:';
$aQuestion = [
"covered" => "列出文章涵盖的学科及研究方法总字数不超过100字学科和方法之间用逗号分隔例如肿瘤学,分子生物学,基因组测序,生物信息学分析",
"overview" => "按照学术规范翻译并提炼文章概述整体内容应大于1200字其中应包含文章背景不少于400字其他内容提炼更强调逻辑性、科学术语准确性和表达的严谨性注意内容不要有严重重复采用连贯的段落形式",
"summary" => "针对文章结论生成一个简单总结内容不要和文章概述重复字数150以内",
"title_chinese" => "将标题翻译成中文:内容需自然流畅、口语化、连贯性、学术性"
// ,
// "content" => "将内容翻译成中文,需自然流畅、口语化、连贯性、学术性,保留原文的章节结构和图表编号"
];
//问题处理
$aMessage = [];
foreach($aQuestion as $key => $value){
$sInfo = json_encode([$key => $value],JSON_UNESCAPED_UNICODE);
$sSysMessagePromptInfo = $sSysMessagePrompt.$sInfo;
if($key == "title_chinese"){
$sUserPrompt = '{#title_chinese#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
}
if($key == "content"){
$sUserPrompt = '{#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
}
if(!in_array($key,["title_chinese","content"])){
$sUserPrompt = '标题:{#title_chinese#} 摘要: {#abstract#} 内容: {#content#}';
$sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt);
return [
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
$aMessage[] = [
['role' => 'system', 'content' => $sSysMessagePromptInfo],
['role' => 'user', 'content' => $sUserPrompt]
];
}
return $aMessage;
}
/**
* 构建AI翻译-处理提示词
*/
public function buildTranslatePrompt($aSearch = [])
{
public function buildTranslatePrompt($aSearch = []){
if(empty($aSearch)){
return [];
}
@@ -136,7 +187,6 @@ class OpenAi
];
}
/**
* 构建AI审稿-处理提示词【重要的多次请求问题初始化】
*/
@@ -217,34 +267,6 @@ class OpenAi
['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt]
];
}
/**
* 从文本中提取被```json```和```包裹的JSON内容并解析
* @param string $text 包含JSON代码块的文本
* @param bool $assoc 是否返回关联数组默认true
* @return array|object 解析后的JSON数据失败时返回null
*/
private function extractAndParse($text, $assoc = true)
{
// 使用正则表达式提取JSON代码块
preg_match('/```json\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? '' : $matches[1];
if (empty($jsonContent)) {
// 尝试宽松匹配允许没有json标记
preg_match('/```\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? $text : $matches[1];
}
// 解析JSON
$aData = json_decode($jsonContent, $assoc);
// 检查解析是否成功
if (json_last_error() !== JSON_ERROR_NONE) {
return ['status' => 2,'msg' => "API返回无效JSON: " . json_last_error_msg()];
}
return ['status' => 1,'msg' => 'success','data' => $aData];
}
/**
* CURL 发送请求到 OpenAI【单独】
@@ -253,7 +275,6 @@ class OpenAi
*/
public function curlOpenAI($aParam = []){
//询问AI信息
$aMessage = empty($aParam['messages']) ? [] : $aParam['messages'];
if(empty($aMessage)){
@@ -288,9 +309,7 @@ class OpenAi
curl_setopt($this->curl, CURLOPT_POSTFIELDS,json_encode($data));
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, TRUE) ; // 获取数据返回
// curl_setopt($this->curl, CURLOPT_TIMEOUT, $this->timeout);
$result = curl_exec($this->curl);
//请求失败
if (curl_errno($this->curl)){
$this->sError = curl_errno($this->curl);
@@ -325,7 +344,7 @@ class OpenAi
/**
* 对接OPENAI接口-并行CURL请求【重要维度单独询问】
*/
public function curlMultiOpenAIImportant($aSearch = [],$timeout = 60, $iChunkSize = 5) {
public function curlMultiOpenAIImportant($aSearch = [],$timeout = 120, $iChunkSize = 2) {
// 入参校验
if (empty($aSearch)) {
return json_encode(['status' => 2, 'msg' => 'Parameter is empty']);
@@ -373,18 +392,19 @@ class OpenAi
CURLOPT_URL => $this->sUrl,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->sApiKey
'Authorization: Bearer ' . $this->sApiKey,
'Expect:',
],
CURLOPT_PROXY => $this->proxy,
// SSL验证优化若代理证书不可信临时关闭生产环境需配置信任证书
CURLOPT_SSL_VERIFYPEER => false, // 调试时设为false生产环境设为true
CURLOPT_SSL_VERIFYHOST => 0, // 调试时设为0生产环境设为2
CURLOPT_SSL_VERIFYPEER => true, // 调试时设为false生产环境设为true
CURLOPT_SSL_VERIFYHOST => 2, // 调试时设为0生产环境设为2
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($aQuestionInfo),
CURLOPT_RETURNTRANSFER => true,
// 超时优化:延长响应超时,新增连接超时
CURLOPT_TIMEOUT => $timeout, // 总超时建议60-120
CURLOPT_CONNECTTIMEOUT => 10, // 连接超时(秒),避免无限等待
CURLOPT_CONNECTTIMEOUT => 20, // 连接超时(秒),避免无限等待
CURLOPT_LOW_SPEED_LIMIT => 1024, // 最低速度(字节/秒),低于此值触发超时
CURLOPT_LOW_SPEED_TIME => 30, // 持续低速时间(秒),超过则终止
]);
@@ -516,14 +536,228 @@ class OpenAi
//日志记录
$this->addLog($aParam);
return json_encode($aParam);
}
/**
* CURL 发送请求到 OpenAI【流式】
* @param $messages 内容
* @param $model 模型类型
*/
public function curlOpenAIStream($aParam = []){
//询问AI信息
$aMessage = empty($aParam['messages']) ? [] : $aParam['messages'];
if(empty($aMessage)){
return json_encode(['status' => 2,'msg' => 'AI Q&A content not obtained']);
}
//模型
$model = empty($aParam['model']) ? 'gpt-4' : $aParam['model'];
//超时设置
$timeout = empty($aParam['timeout']) ? 300 : $aParam['timeout'];
//接口地址
$sUrl = $this->sUrl;
//组装数据
$data = [
'model' => $model,
'messages' => $aMessage,
'temperature' => 0.2,// 降低随机性0-10为最确定
'stream' => true // 关键:启用流式传输,避免超时
];
// Curl通用配置
$this->curl = curl_init();
curl_setopt($this->curl, CURLOPT_URL, $this->sUrl);
curl_setopt($this->curl, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer ' . $this->sApiKey
]);
// 代理与SSL配置根据你的服务器环境调整
curl_setopt($this->curl, CURLOPT_PROXY, $this->proxy);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, 2);
// 超时与传输配置
curl_setopt($this->curl, CURLOPT_POST, true);
curl_setopt($this->curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($this->curl, CURLOPT_TIMEOUT, $timeout); // CURL超时
curl_setopt($this->curl, CURLOPT_CONNECTTIMEOUT, 30); // 连接超时
// === 5. 流式响应处理(核心避免超时) ===
$streamContent = ''; // 累积流式返回的内容
// 回调函数:每收到一块数据就处理并保存,避免整段等待
curl_setopt($this->curl, CURLOPT_WRITEFUNCTION, function ($curl, $data) use (&$streamContent) {
$streamContent .= $data;
return strlen($data); // 必须返回数据长度否则CURL会中断
});
//执行请求
$result = curl_exec($this->curl);
//获取错误信息
$curlErrno = curl_errno($this->curl);
//获取Code码
$httpCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
//关闭连接
curl_close($this->curl);
//错误处理
if (!empty($curlErrno)) {
// 超时但已有部分数据:保存进度,下次从该块重试
if ($curlErrno == CURLE_OPERATION_TIMEDOUT && !empty($streamContent)) {
return json_encode([
'status' => 3,
'msg' => "处理超时,已保存进度",
]);
}
// 其他错误(如网络问题)
return json_encode([
'status' => 4,
'msg' => "OPENAI Error:".curl_error($this->curl)
]);
}
//处理流式结果
$sStreamResponse = $this->parseMedicalStreamResponse($streamContent);
return json_encode(['status' => 1,'msg' => 'success','data' => $sStreamResponse]);
}
/**
* 解析流式响应
*/
private function parseMedicalStreamResponse($streamContent)
{
$fullContent = '';
$lines = explode("\n", $streamContent);
foreach ($lines as $line) {
$line = trim($line);
if (strpos($line, 'data: ') === 0 && $line !== 'data: [DONE]') {
$jsonStr = substr($line, 6); // 去掉"data: "前缀
$jsonData = json_decode($jsonStr, true);
$fullContent .= $jsonData['choices'][0]['delta']['content'] ?? '';
}
}
return $fullContent;
}
/**
* 记录处理进度【Redis】
*/
private function recordProcessingStart($key,$totalQuestions)
{
$this->redis->hMSet($key, [
'status' => 'processing',
'total' => $totalQuestions,
'completed' => 0,
'start_time' => time()
]);
$this->redis->expire($key, 86400); // 24小时过期
}
/**
* 更新处理进度【Redis】
*/
private function updateProcessingProgress($key,$iId,$completed)
{
$this->redis->hSet($key, 'completed', $completed);
//完成进度
$iProgress = round(($completed / $this->redis->hGet($key, 'total')) * 100, 2);
if($iProgress == 100){
$this->recordProcessingComplete($key,$iId);
}
$this->redis->hSet($key, 'progress', $iProgress);
}
/**
* 记录处理完成【Redis】
*/
private function recordProcessingComplete($key,$iId)
{
$this->redis->hSet($key, 'status', 'completed');
$this->redis->hSet($key, 'end_time', time());
$this->wechatGegnerate(['article_id' => $iId]);
}
/**
* 保存分块进度【Redis】
*/
private function saveChunkProgress($key, $chunkIndex, $content)
{
$this->redis->hset($key, "chunk_{$chunkIndex}", $content);
$this->redis->expire($key, 86400); // 进度保存24小时
}
/**
* 微信公众号-生成公微内容(CURL)
*/
public function createWechatContent($aParam = [])
{
//主键ID
$iId = empty($aParam['redis_id']) ? 0 : $aParam['redis_id'];
if(empty($iId)){
return json_encode(['status' => 2, 'msg' => 'Please select an article']);
}
//提问信息
$aMessage = empty($aParam['messages']) ? [] : $aParam['messages'];
if (empty($aMessage)) {
return json_encode(['status' => 2, 'msg' => 'AI Q&A content not obtained']);
}
//记录处理开始
$iNum = count($aMessage);
$sRedisKey = 'ai_create_article_'.$iId;
$this->recordProcessingStart($sRedisKey,$iNum);
//定义空数组
$aChunkResult = $aFail = [];
foreach ($aMessage as $key => $value) {
$aParam['messages'] = $value;
$aParam['chunkIndex'] = $key;
$aParam['count_num'] = $iNum;
Queue::push('app\api\job\createFieldForQueue@fire', $aParam, 'createFieldForQueue');
}
return json_encode(['status' => 1, 'msg' => 'createFieldForQueue success']);
}
/**
* 微信公众号-生成内容队列形式
*/
public function createFieldForQueue($aParam = []){
//主键ID
$iId = empty($aParam['redis_id']) ? 0 : $aParam['redis_id'];
if(empty($iId)){
return json_encode(['status' => 2, 'msg' => 'Please select an article']);
}
//提问信息
$aMessage = empty($aParam['messages']) ? [] : $aParam['messages'];
if (empty($aMessage)) {
return json_encode(['status' => 2, 'msg' => 'AI Q&A content not obtained']);
}
//最大执行数
$iMaxNum = empty($aParam['count_num']) ? 0 : $aParam['count_num'];
//请求OPENAI
$aResult = $this->curlOpenAIStream($aParam);
//更新处理进度
$iIndex = empty($aParam['chunkIndex']) ? 0 : $aParam['chunkIndex'];
$sRedisKey = 'ai_create_article_'.$iId;
$this->updateProcessingProgress($sRedisKey,$iId,$iIndex + 1);
//保存内容
$sRedisKey = 'ai_create_article_progress_'.$iId;
$this->saveChunkProgress($sRedisKey, $iIndex,$aResult);
//更新入库
$aReturnData = json_decode($aResult,true);
$aDataInfo =empty($aReturnData['data']) ? [] : $aReturnData['data'];
$aData = empty($aDataInfo) ? [] : $this->extractAndParse($aDataInfo);
$aData = empty($aData['data']) ? [] : $aData['data'];
if(!empty($aData)){
$aData['article_id'] = $iId;
$this->updateAiArticle($aData);
}
return $aResult;
}
/**
* 获取期刊内容
*/
public function getJournalPaperArt($aParam = []){
//判断文章ID
@@ -585,4 +819,106 @@ class OpenAi
return DB::name('openapi_log')->insertGetId($aInsert);
}
/**
* 更新AI生成内容入库
* @param $messages 内容
* @param $model 模型类型
*/
private function updateAiArticle($aParam = []){
//文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
//查询内容是否存在
$aWhere = ['is_delete' => 2];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select the article to be modified']);
}
$aWhere['article_id'] = $iArticleId;
$aAiArticle = Db::name('ai_article')->field('ai_article_id')->where($aWhere)->find();
if(empty($aAiArticle)){
return json_encode(['status' => 3,'msg' => 'he article content of WeChat official account has not been generated']);
}
$iAiArticleId = $aAiArticle['ai_article_id'];
//必填参数验证
$aFields = ['article_id','title_english','title_chinese','journal_issn','covered','digest','research_result','content','highlights','discussion','prospect','research_background','discussion_results','research_method','overview','summary','is_generate'];
$sFiled = '';
$aUpdateParam = [];
foreach($aFields as $val){
if(!isset($aParam[$val])){
continue;
}
if(is_array($aParam[$val])){
$aParam[$val] = implode(";",$aParam[$val]);
}
$aUpdateParam[$val] = empty($aParam[$val]) ? '' : addslashes($aParam[$val]);
}
if(empty($aUpdateParam)){
return json_encode(['status' => 1,'msg' => 'No data currently being processed']);
}
//执行入库
$aUpdateParam['update_time'] = time();
$result = Db::name('ai_article')->where('ai_article_id',$iAiArticleId)->limit(1)->update($aUpdateParam);
if($result === false){
return json_encode(['status' => 4,'msg' => 'UPDATEING AI article failed']);
}
return json_encode(['status' => 1,'msg' => 'No data currently being processed']);
}
private function wechatGegnerate($aParam = []){
//文章ID
$iArticleId = empty($aParam['article_id']) ? 0 : $aParam['article_id'];
if(empty($iArticleId)){
return json_encode(['status' => 2,'msg' => 'Please select the article to be modified']);
}
//更新生成状态
$aParam['is_generate'] = 1;
$aResult = json_decode($this->updateAiArticle($aParam),true);
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
$sMsg = empty($aResult['msg']) ? '更新状态失败' : $aResult['msg'];
if($iStatus == 1){
//四小时后推送上传素材并推送草稿箱
$iDelaySeconds = 4 * 3600; // 4小时的秒数
Queue::later($iDelaySeconds,'app\api\job\WechatMaterial@fire', ['article_id' => $iArticleId], 'WechatMaterial');
$sMsg = '文章AI内容生成成功';
}else{
$iStatus = 2;
}
//插入日志记录
$oMaterial = new Material;
$aLogInfo = ['article_id' => $iArticleId,'type' => 5,'msg' =>$sMsg,'status' => $iStatus,'create_time' => time()];
$result = json_decode($oMaterial->addWechatLog($aLogInfo),true);
return json_encode($aResult);
}
/**
* 从文本中提取被```json```和```包裹的JSON内容并解析
* @param string $text 包含JSON代码块的文本
* @param bool $assoc 是否返回关联数组默认true
* @return array|object 解析后的JSON数据失败时返回null
*/
private function extractAndParse($text, $assoc = true){
// 使用正则表达式提取JSON代码块
preg_match('/```json\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? '' : $matches[1];
if (empty($jsonContent)) {
// 尝试宽松匹配允许没有json标记
preg_match('/```\s*(\{.*?\})\s*```/s', $text, $matches);
$jsonContent = empty($matches[1]) ? $text : $matches[1];
}
// 解析JSON
$aData = json_decode($jsonContent, $assoc);
// 检查解析是否成功
if (json_last_error() !== JSON_ERROR_NONE) {
return ['status' => 2,'msg' => "API返回无效JSON: " . json_last_error_msg()];
}
return ['status' => 1,'msg' => 'success','data' => $aData];
}
}