[ 'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。 请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构:{ "journal_scope": { "assessment": "是/否", "explanation": "请详细解释说明" } }', "criteria" => "根据文章的标题:{title};摘要:{abstrart}以及期刊范围:{scope}来判断文章是否符合目标期刊{journal_name}" ], "attribute" => [ 'system' => '你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。 请针对问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下JSON结构:{ "attribute": { "assessment": "是/否", "explanation": "请总结归纳分析" } }', "criteria" => "请结合以下几点【研究内容的原创性:论文中的研究内容是否与已有的研究重复?是否在同样的领域提出了类似的结论,但在方法或结果上有所创新?如果有,作者是否清楚地解释了如何与之前的研究不同,或者如何在原有基础上进行扩展或改进?如果是综述文章,汇总并综合最新的研究成果,尤其是近几年内的重要发现,展示领域内最新的进展成果。作者可以识别出未被充分讨论的问题或提出新的研究问题,而不是简单文献堆砌。文章中的图表创新能否将信息的清晰呈现,方便读者理解复杂研究问题。论文方法创新性评估要点:是否采用了新的实验模型或创新的实验设计,能有效解决当前研究中的难点或空白?是否有合理的对照组和多组实验设计,确保研究结果的可靠性?是否使用了当前前沿的技术(如高通量测序、CRISPR基因编辑等),提高了实验精度或数据分析能力?是否结合了跨学科的方法(如生物信息学、人工智能等)?是否应用了多种验证手段或统计方法,确保结果的可信度?是否通过细胞实验、动物模型等多重验证,确保实验结果的可靠性?结论与数据的创新性:研究结论是否提出了新观点或新见解?是否提供了新的实验数据或观察结果,能够突破当前的研究局限?例如,发现了新的生物标志物,或对已知生物通路的作用机制提供了全新的解释】评估文章内容{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 = []){ if(empty($aSearch)){ return []; } $sSysMessagePrompt = ' 你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,请返回中文解释!返回格式必须严格遵循以下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); } $aMessage[] = [ ['role' => 'system', 'content' => $sSysMessagePromptInfo], ['role' => 'user', 'content' => $sUserPrompt] ]; } return $aMessage; } /** * 构建公微模版-处理提示词【Review】 */ public function buildReviewPrompt($aSearch = []){ if(empty($aSearch)){ return []; } $sSysMessagePrompt = ' 你是一位专业的医学学术翻译与分析专家,擅长将复杂的医学研究论文转化为适合微信公众号推送的专业科普内容。请根据提供的医学论文信息,按照以下严格格式生成结构化输出[中文]:'; $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); } $aMessage[] = [ ['role' => 'system', 'content' => $sSysMessagePromptInfo], ['role' => 'user', 'content' => $sUserPrompt] ]; } return $aMessage; } /** * 构建AI翻译-处理提示词 */ public function buildTranslatePrompt($aSearch = []){ if(empty($aSearch)){ return []; } $sSysMessagePrompt = '你是一位专业的医学翻译专家,请将用户提供的内容准确、流畅地翻译成中文。翻译需自然流畅、口语化、连贯性、学术性,保留原文的专业术语和逻辑结构'; $sUserPrompt = '"将以下内容翻译为中文,仅返回翻译结果,不要解释:\n {#content#}"'; $sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt); return [ ['role' => 'user', 'content' => $sUserPrompt] ]; } /** * 构建AI审稿-处理提示词【重要的多次请求问题初始化】 */ public function buildReviewPromptImportant($aSearch = [],$aValue = []) { //必填验证 if(empty($aSearch) || empty($aValue)){ return []; } //组装问题 $sUserPrompt = empty($aValue['criteria']) ? '' : $aValue['criteria']; $sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt); $aMessage = [ ['role' => 'system', 'content' => $aValue['system']], ['role' => 'user', 'content' => $sUserPrompt] ]; //模型版本 $model = empty($aSearch['model']) ? 'gpt-4.1' : $aSearch['model']; $aMessage = [ 'model' => $model, 'messages' => $aMessage, 'temperature' => 0.2,// 降低随机性(0-1,0为最确定) ]; return $aMessage; } /** * 构建AI审稿-处理提示词【非重要的一次请求回答】 */ public function buildReviewPromptUnimportant($aSearch = []) { if(empty($aSearch)){ return []; } $sSysMessagePrompt = ' 你是一位资深的学术评审专家,负责严谨、客观地评估学术文章。请根据提供的信息,对以下问题进行专业评审 ```json { "ethics": "根据文章内容分析是否存在伦理号缺失或明显伦理问题?", "academic": "根据文章内容分析是否存在学术不端问题【抄袭、数据作假、图片伪造等】?", "conclusion": "根据文章内容判断文章结论科学性和可靠性?", "fund_number":"根据文章内容分析有无基金号?请详细说明", "hotspot":"根据文章内容分析有哪些符合目标期刊当下的热点话题", "submit_direction":"根据文章内容总结文章送审方向", "references_num":"根据文章内容统计文章参考文献的数量", "references_past_three":"统计文章内容里近3年的参考文献的数量及所占比例", "references_past_five":"统计文章内容里近5年的参考文献的数量及所占比例", "references_ratio_JCR1":"根据2024JCR最新分区分析文章内容里的文献判断属于JCR1还是JCR2,统计属于JCR 1区的数量及比例", "references_ratio_JCR2":"根据2024JCR最新分区评估文章内容里的文献判断属于JCR1还是JCR2,统计属于JCR 2区的数量及比例", "registration_assessment":"根据文章内容分析是否存在临床注册号和知情同意书?解释说明", "cite_rate" => "根据文章内容分析文章发表后被引用的概率" }请针对每个问题提供客观、专业的评估,并给出简要的理由。请返回中文解释!返回格式必须严格遵循以下 JSON 结构:{ "ethics": { "assessment": "是/否", "explanation": "请针对文章伦理号及伦理进行分析及说明" }, "academic": { "assessment": "是/否", "explanation": "请进行相似度分析并指出具体可疑部分" }, "conclusion": { "assessment": "是/否", "explanation": "是否基于充分证据得出" }, "fund_number": "", "hotspot": "", "submit_direction": "", "references_num":"", "references_past_three": "", "references_past_five": "", "references_ratio_JCR1": "", "references_ratio_JCR2": "", "registration_assessment":"", "cite_rate" => "" }'; $sUserPrompt = '文章内容{content};目标期刊{journal_name}'; $sUserPrompt = str_replace(array_keys($aSearch), array_values($aSearch), $sUserPrompt); return [ ['role' => 'system', 'content' => $sSysMessagePrompt], ['role' => 'user', 'content' => $sUserPrompt] ]; } /** * CURL 发送请求到 OpenAI【单独】 * @param $messages 内容 * @param $model 模型类型 */ public function curlOpenAI($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.1' : $aParam['model']; //接口地址 $sUrl = $this->sUrl; //组装数据 $data = [ 'model' => $model, 'messages' => $aMessage, 'temperature' => 0.2,// 降低随机性(0-1,0为最确定) ]; $this->curl = curl_init(); // 通用配置 curl_setopt($this->curl, CURLOPT_URL, $sUrl); // 设置头信息 curl_setopt($this->curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->sApiKey ]); 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); //设置为POST方式 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); curl_close($this->curl); return json_encode(['status' => 3,'OPENAI Error:'.$this->sError]); } $aResult = json_decode($result,true); //处理返回信息 $aData = empty($aResult['choices']) ? [] : $aResult['choices']; if(empty($aData)){ return json_encode(['status' => 5,'msg' => 'OPENAI returns empty content']); } $aData = empty($aData[0]) ? [] : $aData[0]; if(empty($aData)){ return json_encode(array('status' => 6,'msg' => 'OPENAI did not return data')); } $aData = empty($aData['message']) ? [] : $aData['message']; $aData = empty($aData['content']) ? [] : $aData['content']; if(empty($aData)){ return json_encode(array('status' => 7,'msg' => 'OPENAI did not return data')); } //数据转换 $aData = $this->extractAndParse($aData); $aContent = empty($aData['data']) ? [] : $aData['data']; $sMsg = empty($aData['msg']) ? 'OPENAI did not return data' : $aData['msg']; if(empty($aContent)){ return json_encode(array('status' => 8,'msg' => $sMsg)); } curl_close($this->curl); return json_encode(['status' => 1,'msg' => 'success','data' => $aContent]); } /** * 对接OPENAI接口-并行CURL请求【重要维度单独询问】 */ public function curlMultiOpenAIImportant($aSearch = [],$timeout = 120, $iChunkSize = 2) { // 入参校验 if (empty($aSearch)) { return json_encode(['status' => 2, 'msg' => 'Parameter is empty']); } //提问问题类型 $sKey = empty($aSearch['question']) ? '' : $aSearch['question']; if (empty($sKey)) { return json_encode(['status' => 2, 'msg' => 'Please select the type of question']); } //获取问题 $aQuestion = $this->$sKey; if (empty($aQuestion)) { return json_encode(['status' => 2, 'msg' => 'question is empty']); } //分批处理(核心优化:控制并发量) $aChunk = array_chunk($aQuestion, $iChunkSize); // 按批次拆分,每批最多5个请求 //定义空数组用于接收数据 $aEmptyData = $aLog = $aReturnData = []; //分批次处理开始 foreach ($aChunk as $iChunkKey => $item) { // 初始化多curl句柄 $oCurlMulti = curl_multi_init(); $aCurl = []; // 批量初始化请求 foreach ($item as $key => $value) { // 跳过无效参数 if (empty($value)) { $aLog[] = [ 'content' => $iChunkKey.'-'.$key.':Invalid parameter' ]; continue; } //问题处理-变量替换 $aQuestionInfo = $this->buildReviewPromptImportant($aSearch,$value); if(empty($aQuestionInfo)){ $aLog[] = [ 'content' => $iChunkKey.'-'.$key.':The problem is empty:'.json_encode($value) ]; continue; } // 核心配置优化 $oCurl = curl_init(); curl_setopt_array($oCurl, [ CURLOPT_URL => $this->sUrl, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $this->sApiKey, 'Expect:', ], CURLOPT_PROXY => $this->proxy, // SSL验证优化:若代理证书不可信,临时关闭(生产环境需配置信任证书) 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 => 20, // 连接超时(秒),避免无限等待 CURLOPT_LOW_SPEED_LIMIT => 1024, // 最低速度(字节/秒),低于此值触发超时 CURLOPT_LOW_SPEED_TIME => 30, // 持续低速时间(秒),超过则终止 ]); curl_multi_add_handle($oCurlMulti, $oCurl); $aCurl[$key] = $oCurl; } // 空请求处理 if (empty($aCurl)) { curl_multi_close($oCurlMulti); continue; } // 核心优化:修复curl_multi循环逻辑,确保所有请求完成 $active = null; $mrc = CURLM_OK; // 第一阶段:处理瞬时可完成的请求 do { $mrc = curl_multi_exec($oCurlMulti, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); // 第二阶段:等待所有活跃请求完成(关键优化) while ($active > 0 && $mrc == CURLM_OK) { // 等待事件(超时1秒,避免CPU空转) if (curl_multi_select($oCurlMulti, 1.0) != -1) { // 处理就绪的请求 do { $mrc = curl_multi_exec($oCurlMulti, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } else { // 无事件时,检查是否超时(防止无限阻塞) $timedOut = false; foreach ($aCurl as $oCurl) { $startTime = curl_getinfo($oCurl, CURLINFO_STARTTRANSFER_TIME); if ($startTime > 0 && (microtime(true) - $startTime) > $timeout) { $timedOut = true; break; } } if ($timedOut) break; // 超时则强制退出 } } // 处理当前批次结果 foreach ($aCurl as $key => $oCurl) { // 1. 捕获curl错误(连接失败、超时等) $sError = curl_error($oCurl); if (!empty($sError)) { $aLog[] = [ 'content' => "Curl error: {$sError}" ]; $aEmptyData[] = $key; curl_multi_remove_handle($oCurlMulti, $oCurl); curl_close($oCurl); continue; } // 2. 获取HTTP状态码(关键优化:处理OpenAI的API错误) $httpCode = curl_getinfo($oCurl, CURLINFO_HTTP_CODE); $sContent = curl_multi_getcontent($oCurl); // 3. 处理非200状态码(如限流、服务不可用) if ($httpCode != 200) { $errorMsg = "HTTP {$httpCode}: " . (empty($sContent) ? 'No response' : $sContent); // 记录关键错误日志(便于调试) $aLog[] = [ 'http_code' => $httpCode, 'content' => $errorMsg, ]; $aEmptyData[] = $key; curl_multi_remove_handle($oCurlMulti, $oCurl); curl_close($oCurl); continue; } // 4. 解析响应内容(原逻辑优化) $aResult = json_decode($sContent, true); if (json_last_error() != JSON_ERROR_NONE) { $aLog[] = [ 'content' => "Invalid JSON: {$sContent}", ]; $aEmptyData[] = $key; curl_multi_remove_handle($oCurlMulti, $oCurl); curl_close($oCurl); continue; } // 5. 提取OpenAI的content(简化判断逻辑) $aOpenAiContent = empty($aResult['choices'][0]['message']['content']) ? '' : $aResult['choices'][0]['message']['content']; if (empty($aOpenAiContent)) { $aLog[] = [ 'content' => "OPENAI returns empty content", ]; $aEmptyData[] = $key; curl_multi_remove_handle($oCurlMulti, $oCurl); curl_close($oCurl); continue; } // 6. 处理业务解析(原extractAndParse逻辑) $aData = $this->extractAndParse($aOpenAiContent); $aContent = empty($aData['data']) ? [] : $aData['data']; $sMsg = empty($aData['msg']) ? 'Success' : $aData['msg']; if (empty($aContent)) { $aEmptyData[] = $key; } $aLog[] = [ 'content' => $sMsg, ]; $aReturnData += $aContent; // 释放资源 curl_multi_remove_handle($oCurlMulti, $oCurl); curl_close($oCurl); } // 关闭当前批次的multi句柄 curl_multi_close($oCurlMulti); // 批次间隔(核心优化:避免触发OpenAI限流) if ($iChunkKey < count($aChunk) - 1) { usleep(1000000); // 批次间间隔1秒(根据OpenAI配额调整) } } $aParam = [ 'status' => 1, 'msg' => 'success', 'data' => empty($aReturnData) ? [] : $aReturnData, 'empty_data' => empty($aEmptyData) ? [] : $aEmptyData, 'log_data' => empty($aLog) ? [] : $aLog, 'open_ai_id' => empty($aSearch['open_ai_id']) ? 0 : $aSearch['open_ai_id'] ]; //日志记录 $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-1,0为最确定) '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 $sIssn = empty($aParam['issn']) ? [] : $aParam['issn']; if(empty($sIssn)){ return json_encode(['status' => 2,'msg' => 'Please select an article']); } //接口获取期刊内容 $sUrl = $this->sTmrUrl."/api/Supplementary/getJournalPaperArt"; $aParam = ['issn' => $sIssn]; $aResult = object_to_array(json_decode(myPost($sUrl,$aParam),true)); return json_encode($aResult); } /** * 获取文章文件内容 */ public function getFileContent($aParam = []){ //判断文章ID $iArticleId = empty($aParam['article_id']) ? [] : $aParam['article_id']; if(empty($iArticleId)){ return json_encode(['status' => 2,'msg' => 'Please select an article']); } //获取文件内容 $aWhere = ['article_id' => $iArticleId,'type_name' => 'manuscirpt']; $aFile = Db::name('article_file')->field('file_url')->where($aWhere)->order('ctime desc')->limit(1)->find(); if(empty($aFile['file_url'])){ return json_encode(['status' => 2,'msg' => 'No Manuscript']); } //接口获取上传文件 $sUrl = $this->sJavaUrl."api/typeset/readDocx"; $aParam['fileRoute'] = $this->sFileUrl.$aFile['file_url']; $aResult = object_to_array(json_decode(myPost($sUrl,$aParam))); return json_encode($aResult); } /** * 添加接口访问日志 */ private function addLog($aParam = []){ $aField = ['open_ai_id','log_data','empty_data']; $aInsert = []; foreach ($aField as $key => $value) { if(isset($aParam[$value])){ $aInsert[$value] = is_array($aParam[$value]) ? json_encode($aParam[$value]) : $aParam[$value]; } } $result = 0; if(empty($aInsert)){ return true; } $aInsert['create_time'] = time(); 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]; } }