696 lines
30 KiB
PHP
696 lines
30 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use app\api\controller\Base;
|
||
use app\common\CitationRelevanceService;
|
||
use app\common\CrossrefService;
|
||
use app\common\PubmedService;
|
||
use think\Db;
|
||
use think\Env;
|
||
/**
|
||
* @title 参考文献
|
||
* @description 相关方法汇总
|
||
*/
|
||
class References extends Base
|
||
{
|
||
public function __construct(\think\Request $request = null) {
|
||
parent::__construct($request);
|
||
}
|
||
//OPENAI token
|
||
private $sApiKey = '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';
|
||
//OPENAI URL
|
||
private $sApiUrl = 'https://api.openai.com/v1/chat/completions';
|
||
/**
|
||
* 获取参考文献的信息
|
||
* @param p_refer_id 主键ID
|
||
*/
|
||
public function get($aParam = []){
|
||
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
|
||
//必填值验证
|
||
$iPReferId = empty($aParam['p_refer_id']) ? '' : $aParam['p_refer_id'];
|
||
if(empty($iPReferId)){
|
||
return json_encode(['status' => 2,'msg' => 'Please select the reference to be queried']);
|
||
}
|
||
$aWhere = ['p_refer_id' => $iPReferId,'state' => 0];
|
||
$aRefer = Db::name('production_article_refer')->where($aWhere)->find();
|
||
if(empty($aRefer)){
|
||
return json_encode(['status' => 4,'msg' => 'Reference is empty']);
|
||
}
|
||
//获取文章信息
|
||
$aParam['p_article_id'] = $aRefer['p_article_id'];
|
||
$aArticle = $this->getArticle($aParam);
|
||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||
if($iStatus != 1){
|
||
return json_encode($aArticle);
|
||
}
|
||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||
if(empty($aArticle)){
|
||
return json_encode(['status' => 3,'msg' => 'The article does not exist']);
|
||
}
|
||
|
||
//获取参考文献信息作者名.文章题目.期刊名缩写.年卷页.Available at: //https://doi.org/xxxxx
|
||
//作者
|
||
$sData = $aRefer['refer_frag'];
|
||
if($aRefer['refer_type'] == 'journal'){
|
||
if(!empty($aRefer['doilink'])){
|
||
$sAuthor = empty($aRefer['author']) ? '' : trim(trim($aRefer['author']),'.');
|
||
if(!empty($sAuthor)){
|
||
$aAuthor = explode(',', $sAuthor);
|
||
if(count($aAuthor) > 3){
|
||
$sAuthor = implode(',', array_slice($aAuthor, 0,3));
|
||
$sAuthor .= ', et al';
|
||
}
|
||
if(count($aAuthor) <= 3 ){
|
||
$sAuthor = implode(',', $aAuthor);
|
||
}
|
||
}
|
||
//文章标题
|
||
$sTitle = empty($aRefer['title']) ? '' : trim(trim($aRefer['title']),'.');
|
||
//期刊名缩写
|
||
$sJoura = empty($aRefer['joura']) ? '' : trim(trim($aRefer['joura']),'.');
|
||
//年卷页
|
||
$sDateno = empty($aRefer['dateno']) ? '' : trim(trim($aRefer['dateno']),'.');
|
||
//DOI
|
||
$sDoilink = empty($aRefer['doilink']) ? '' : trim($aRefer['doilink']);
|
||
if(!empty($sDoilink)){
|
||
$sDoilink = strpos($sDoilink ,"http")===false ? "https://doi.org/".$sDoilink : $sDoilink;
|
||
$sDoilink = str_replace('http://doi.org/', 'https://doi.org/', $sDoilink);
|
||
}
|
||
$sReferDoi = empty($aRefer['refer_doi']) ? '' : trim($aRefer['refer_doi']);
|
||
if(!empty($sReferDoi)){
|
||
$sReferDoi = strpos($sReferDoi ,"http")===false ? "https://doi.org/".$sReferDoi : $sReferDoi;
|
||
$sReferDoi = str_replace('http://doi.org/', 'https://doi.org/', $sReferDoi);
|
||
}
|
||
$sDoilink = empty($sDoilink) ? $sReferDoi : $sDoilink;
|
||
|
||
$sData = $sAuthor.'.'.$sTitle.'.'.$sJoura.'.'.$sDateno.".Available at:\n".$sDoilink;
|
||
}
|
||
}
|
||
if($aRefer['refer_type'] == 'book'){
|
||
$sAuthor = empty($aRefer['author']) ? '' : trim(trim($aRefer['author']),'.');
|
||
if(!empty($sAuthor)){
|
||
$aAuthor = explode(',', $sAuthor);
|
||
if(count($aAuthor) > 3){
|
||
$sAuthor = implode(',', array_slice($aAuthor, 0,3));
|
||
$sAuthor .= ', et al';
|
||
}
|
||
if(count($aAuthor) <= 3 ){
|
||
$sAuthor = implode(',', $aAuthor);
|
||
}
|
||
}
|
||
//文章标题
|
||
$sTitle = empty($aRefer['title']) ? '' : trim(trim($aRefer['title']),'.');
|
||
//期刊名缩写
|
||
$sJoura = empty($aRefer['joura']) ? '' : trim(trim($aRefer['joura']),'.');
|
||
//年卷页
|
||
$sDateno = empty($aRefer['dateno']) ? '' : trim(trim($aRefer['dateno']),'.');
|
||
//DOI
|
||
$sDoilink = empty($aRefer['isbn']) ? '' : trim($aRefer['isbn']);
|
||
|
||
$sData = $sAuthor.'.'.$sTitle.'.'.$sJoura.'.'.$sDateno.".Available at:\n".$sDoilink;
|
||
}
|
||
$aRefer['deal_content'] = $sData;
|
||
return json_encode(['status' => 1,'msg' => 'success','data' => $aRefer]);
|
||
}
|
||
|
||
/**
|
||
* 参考文献鉴别:正文引用上下文 + PubMed/Crossref + 大模型向量相似度
|
||
* 参数:p_refer_id(必填)
|
||
* 环境变量(可选):citation_chat_url、citation_chat_model、citation_chat_api_key、citation_chat_timeout、crossref_mailto、pubmed_email
|
||
*/
|
||
public function checkCitationRelevance($aParam = [])
|
||
{
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
$pReferId = intval(isset($aParam['p_refer_id']) ? $aParam['p_refer_id'] : 0);
|
||
if (!$pReferId) {
|
||
return jsonError('p_refer_id is required');
|
||
}
|
||
|
||
$refer = Db::name('production_article_refer')
|
||
->where('p_refer_id', $pReferId)
|
||
->where('state', 0)
|
||
->find();
|
||
if (empty($refer)) {
|
||
return jsonError('Reference not found');
|
||
}
|
||
|
||
$aArticle = $this->getArticle(['p_article_id' => $refer['p_article_id']]);
|
||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||
if ($iStatus != 1) {
|
||
return json_encode($aArticle);
|
||
}
|
||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||
if (empty($aArticle['article_id'])) {
|
||
return jsonError('Article not found');
|
||
}
|
||
|
||
$articleId = intval($aArticle['article_id']);
|
||
$mains = Db::name('article_main')
|
||
->where('article_id', $articleId)
|
||
->whereIn('state', [0, 2])
|
||
->order('sort asc')
|
||
->select();
|
||
if (empty($mains)) {
|
||
return jsonError('article_main is empty');
|
||
}
|
||
|
||
$citationMark = intval($refer['index']) + 1;
|
||
$context = $this->extractCitationContextFromMains($mains, $citationMark);
|
||
if ($context === '') {
|
||
return jsonError('Citation context not found in article_main for mark [' . $citationMark . ']');
|
||
}
|
||
|
||
$apiKey = trim((string)Env::get('citation_chat_api_key', ''));
|
||
if ($apiKey === '') {
|
||
return jsonError('Please set env citation_chat_api_key for embedding via chat');
|
||
}
|
||
|
||
$config = [
|
||
'chat_url' => trim((string)Env::get('citation_chat_url', 'http://chat.taimed.cn/v1/chat/completions')),
|
||
'chat_model' => trim((string)Env::get('citation_chat_model', 'DeepSeek-Coder-V2-Instruct')),
|
||
'timeout' => max(60, intval(Env::get('citation_chat_timeout', 180))),
|
||
'embedding_dim' => max(32, intval(Env::get('citation_embedding_dim', 256))),
|
||
'embedding_headers' => [
|
||
'Authorization: Bearer ' . $apiKey,
|
||
],
|
||
];
|
||
|
||
$pubmed = new PubmedService([
|
||
'email' => trim((string)Env::get('pubmed_email', '')),
|
||
'tool' => trim((string)Env::get('pubmed_tool', 'tmrjournals')),
|
||
]);
|
||
$crossref = new CrossrefService([
|
||
'mailto' => trim((string)Env::get('crossref_mailto', '')),
|
||
]);
|
||
$svc = new CitationRelevanceService($pubmed, $crossref, $config);
|
||
|
||
$qc = $svc->checkOne($context, $refer, []);
|
||
|
||
return jsonSuccess([
|
||
'p_refer_id' => $pReferId,
|
||
'citation_mark' => $citationMark,
|
||
'refer_index' => intval($refer['index']),
|
||
'context' => $context,
|
||
'problem_flag' => $qc['problem_flag'] ?? '',
|
||
'problem_reason' => $qc['problem_reason'] ?? '',
|
||
'relevance_flag' => $qc['relevance_flag'] ?? '',
|
||
'relevance_score'=> $qc['relevance_score'] ?? 0,
|
||
'reason' => $qc['reason'] ?? '',
|
||
'pubmed' => $qc['pubmed'] ?? [],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 从 t_article_main 拼接正文,按 [n] 定位句子并取前后各 1 句作为上下文
|
||
*/
|
||
private function extractCitationContextFromMains(array $mains, int $citationMark): string
|
||
{
|
||
if ($citationMark <= 0) {
|
||
return '';
|
||
}
|
||
$chunks = [];
|
||
foreach ($mains as $row) {
|
||
$text = isset($row['content']) ? (string)$row['content'] : '';
|
||
if ($text === '') {
|
||
continue;
|
||
}
|
||
$text = preg_replace('/<\s*\/?\s*blue[^>]*>/i', '', $text);
|
||
$text = strip_tags($text);
|
||
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||
$text = preg_replace('/\s+/u', ' ', trim($text));
|
||
if ($text !== '') {
|
||
$chunks[] = $text;
|
||
}
|
||
}
|
||
$fullText = implode("\n", $chunks);
|
||
if ($fullText === '') {
|
||
return '';
|
||
}
|
||
|
||
$sentences = $this->splitEnglishSentences($fullText);
|
||
$pattern = '/\[' . preg_quote((string)$citationMark, '/') . '\]/';
|
||
foreach ($sentences as $si => $sent) {
|
||
if (!preg_match($pattern, $sent)) {
|
||
continue;
|
||
}
|
||
$start = max(0, $si - 1);
|
||
$end = min(count($sentences) - 1, $si + 1);
|
||
$ctx = implode(' ', array_slice($sentences, $start, $end - $start + 1));
|
||
return trim(preg_replace('/\s+/u', ' ', $ctx));
|
||
}
|
||
return '';
|
||
}
|
||
|
||
private function splitEnglishSentences(string $text): array
|
||
{
|
||
$text = trim($text);
|
||
if ($text === '') {
|
||
return [];
|
||
}
|
||
$text = preg_replace('/\s+/u', ' ', $text);
|
||
$parts = preg_split('/(?<=[\.\?\!])\s+/', $text);
|
||
$out = [];
|
||
foreach ($parts as $p) {
|
||
$p = trim((string)$p);
|
||
if ($p !== '') {
|
||
$out[] = $p;
|
||
}
|
||
}
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* 修改参考文献的信息
|
||
* @param p_refer_id 主键ID
|
||
*/
|
||
public function modify($aParam = []){
|
||
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
|
||
//必填值验证
|
||
$iPReferId = empty($aParam['p_refer_id']) ? '' : $aParam['p_refer_id'];
|
||
if(empty($iPReferId)){
|
||
return json_encode(['status' => 2,'msg' => 'Please select the reference to be queried']);
|
||
}
|
||
$sContent = empty($aParam['content']) ? '' : $aParam['content'];
|
||
if(empty($sContent)){
|
||
return json_encode(['status' => 2,'msg' => 'Please enter the modification content']);
|
||
}
|
||
if(!is_string($sContent)){
|
||
return json_encode(['status' => 2,'msg' => 'The content format is incorrect']);
|
||
}
|
||
|
||
//获取参考文献信息
|
||
$aWhere = ['p_refer_id' => $iPReferId,'state' => 0];
|
||
$aRefer = Db::name('production_article_refer')->where($aWhere)->find();
|
||
if(empty($aRefer)){
|
||
return json_encode(['status' => 4,'msg' => 'Reference is empty']);
|
||
}
|
||
|
||
//获取文章信息
|
||
$aParam['p_article_id'] = $aRefer['p_article_id'];
|
||
$aArticle = $this->getArticle($aParam);
|
||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||
if($iStatus != 1){
|
||
return json_encode($aArticle);
|
||
}
|
||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||
if(empty($aArticle)){
|
||
return json_encode(['status' => 3,'msg' => 'The article does not exist']);
|
||
}
|
||
|
||
//数据处理
|
||
$aContent = json_decode($this->dealContent(['content' => $sContent]),true);
|
||
$aUpdate = empty($aContent['data']) ? [] : $aContent['data'];
|
||
if(empty($aUpdate)){
|
||
return json_encode(['status' => 5,'msg' => 'The content format is incorrect']);
|
||
}
|
||
$aUpdate['refer_content'] = $sContent;
|
||
$aUpdate['is_change'] = 1;
|
||
$aUpdate['update_time'] = time();
|
||
//更新数据
|
||
$aWhere = ['p_refer_id' => $iPReferId,'state' => 0];
|
||
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($aUpdate);
|
||
if($result === false){
|
||
return json_encode(['status' => 6,'msg' => 'Update failed']);
|
||
}
|
||
return json_encode(['status' => 1,'msg' => 'success']);
|
||
}
|
||
|
||
|
||
/**
|
||
* 处理参考文献的信息
|
||
* @param p_refer_id 主键ID
|
||
*/
|
||
public function dealContent($aParam = []){
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
//必填验证
|
||
$sContent = empty($aParam['content']) ? '' : $aParam['content'];
|
||
if(empty($sContent)){
|
||
return json_encode(['status' => 2,'msg' => 'Please enter the modification content']);
|
||
}
|
||
if(!is_string($sContent)){
|
||
return json_encode(['status' => 2,'msg' => 'The content format is incorrect']);
|
||
}
|
||
$sContent = str_replace(['?','?'], '.', $sContent);
|
||
$aContent = explode('.', $sContent);
|
||
$aUpdate = [];
|
||
if(count($aContent) > 1){
|
||
$aField = [0 => 'author',1 => 'title', 2 => 'joura',3 => 'dateno'];
|
||
$aStart = array_slice($aContent, 0,4);
|
||
foreach ($aStart as $key => $value) {
|
||
if(empty($value)){
|
||
continue;
|
||
}
|
||
$aUpdate[$aField[$key]] = trim(trim($value),'.');
|
||
}
|
||
|
||
$sDoi = empty(array_slice($aContent, 4)) ? '' : implode('.', array_slice($aContent, 4));
|
||
// 匹配http/https开头的URL正则
|
||
$urlPattern = '/https?:\/\/[^\s<>"]+|http?:\/\/[^\s<>"]+/i';
|
||
// 执行匹配(preg_match_all返回所有结果)
|
||
preg_match_all($urlPattern, $sDoi, $matches);
|
||
if(!empty($matches[0])){
|
||
$sDoi = implode(',', array_unique($matches[0]));
|
||
}
|
||
if(empty($sDoi)){
|
||
return json_encode(['status' => 4,'msg' => 'Reference DOI is empty']);
|
||
}
|
||
$sDoi = trim(trim($sDoi),':');
|
||
$sDoi = strpos($sDoi ,"http")===false ? "https://doi.org/".$sDoi : $sDoi;
|
||
$sDoi = str_replace('http://doi.org/', 'https://doi.org/', $sDoi);
|
||
$aUpdate['doilink'] = $sDoi;
|
||
//$doiPattern = '/10\.\d{4,9}\/[^\s\/?#&=]+/i';
|
||
$doiPattern = '/\b10\.\d+(?:\.\d+)*\/[^\s?#&=]+/i';
|
||
if (preg_match($doiPattern, $sDoi, $matches)) {
|
||
$aUpdate['doi'] = $matches[0];
|
||
$aUpdate['doilink'] = 'https://doi.org/'.''.$aUpdate['doi'];
|
||
}else{
|
||
$aUpdate['doi'] = $sDoi;
|
||
}
|
||
if(!empty($aUpdate['author'])){
|
||
$aUpdate['author'] = trim(trim($aUpdate['author'])).'.';
|
||
}
|
||
|
||
}
|
||
return json_encode(['status' => 1,'msg' => 'success','data' => $aUpdate]);
|
||
}
|
||
|
||
/**
|
||
* 获取文章信息
|
||
*/
|
||
private function getArticle($aParam = []){
|
||
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
|
||
//获取生产文章信息
|
||
$iPArticleId = empty($aParam['p_article_id']) ? 0 : $aParam['p_article_id'];
|
||
if(empty($iPArticleId)){
|
||
return ['status' => 2,'msg' => 'Please select the article to query'];
|
||
}
|
||
$aWhere = ['p_article_id' => $iPArticleId,'state' => ['in',[0,2]]];
|
||
$aProductionArticle = Db::name('production_article')->field('article_id')->where($aWhere)->find();
|
||
$iArticleId = empty($aProductionArticle['article_id']) ? 0 : $aProductionArticle['article_id'];
|
||
if(empty($iArticleId)) {
|
||
return ['status' => 2,'msg' => 'No articles found'];
|
||
}
|
||
|
||
//查询条件
|
||
$aWhere = ['article_id' => $iArticleId,'state' => ['in',[5,6]]];
|
||
$aArticle = Db::name('article')->field('article_id')->where($aWhere)->find();
|
||
if(empty($aArticle)){
|
||
return ['status' => 3,'msg' => 'The article does not exist or has not entered the editorial reference status'];
|
||
}
|
||
$aArticle['p_article_id'] = $iPArticleId;
|
||
return ['status' => 1,'msg' => 'success','data' => $aArticle];
|
||
}
|
||
/**
|
||
* AI检测
|
||
*/
|
||
public function checkByAi($aParam = []){
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
|
||
//获取文章信息
|
||
$aArticle = $this->getArticle($aParam);
|
||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||
if($iStatus != 1){
|
||
return json_encode($aArticle);
|
||
}
|
||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||
if(empty($aArticle)){
|
||
return json_encode(['status' => 3,'msg' => 'The article does not exist']);
|
||
}
|
||
//查询参考文献信息
|
||
$aWhere = ['p_article_id' => $aArticle['p_article_id'],'state' => 0,'doilink' => ''];
|
||
$aRefer = Db::name('production_article_refer')->field('p_refer_id,p_article_id,refer_type,refer_content,doilink,refer_doi')->where($aWhere)->select();
|
||
if(empty($aRefer)){
|
||
return json_encode(['status' => 4,'msg' => 'No reference information found']);
|
||
}
|
||
//数据处理
|
||
foreach ($aRefer as $key => $value) {
|
||
if(empty($value['refer_doi'])){
|
||
continue;
|
||
}
|
||
if($value['refer_doi'] == 'Not Available'){
|
||
continue;
|
||
}
|
||
if($value['refer_type'] == 'journal' && !empty($value['doilink'])){
|
||
continue;
|
||
}
|
||
if($value['refer_type'] == 'book' && !empty($value['isbn'])){
|
||
continue;
|
||
}
|
||
//写入获取参考文献详情队列
|
||
\think\Queue::push('app\api\job\AiCheckReferByDoi@fire',$value,'AiCheckReferByDoi');
|
||
}
|
||
return json_encode(['status' => 1,'msg' => 'Successfully joined the AI inspection DOI queue']);
|
||
}
|
||
/**
|
||
* 获取结果
|
||
*/
|
||
public function getCheckByAiResult($aParam = []){
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
|
||
//必填值验证
|
||
$iPReferId = empty($aParam['p_refer_id']) ? '' : $aParam['p_refer_id'];
|
||
if(empty($iPReferId)){
|
||
return json_encode(['status' => 2,'msg' => 'Please select the reference to be queried']);
|
||
}
|
||
//获取参考文献信息
|
||
$aWhere = ['p_refer_id' => $iPReferId,'state' => 0];
|
||
$aRefer = Db::name('production_article_refer')->field('p_refer_id,p_article_id,refer_type,refer_content,doilink,refer_doi,state,dateno')->where($aWhere)->find();
|
||
if(empty($aRefer)){
|
||
return json_encode(['status' => 4,'msg' => 'Reference is empty'.json_encode($aParam)]);
|
||
}
|
||
if(empty($aRefer['refer_doi'])){
|
||
return json_encode(['status' => 4,'msg' => 'Reference DOI is empty'.json_encode($aParam)]);
|
||
}
|
||
if($aRefer['refer_type'] == 'journal' && !empty($aRefer['doilink'])){
|
||
$aDateno = empty($aRefer['dateno']) ? [] : explode(':', $aRefer['dateno']);
|
||
if(count($aDateno) > 1){
|
||
return json_encode(['status' => 4,'msg' => 'No need to parse again-journal'.json_encode($aParam)]);
|
||
}
|
||
}
|
||
if($aRefer['refer_type'] == 'book' && !empty($aRefer['isbn'])){
|
||
return json_encode(['status' => 4,'msg' => 'No need to parse again-book'.json_encode($aParam)]);
|
||
}
|
||
//获取文章信息
|
||
$aParam['p_article_id'] = $aRefer['p_article_id'];
|
||
$aArticle = $this->getArticle($aParam);
|
||
$iStatus = empty($aArticle['status']) ? 0 : $aArticle['status'];
|
||
if($iStatus != 1){
|
||
return json_encode($aArticle);
|
||
}
|
||
$aArticle = empty($aArticle['data']) ? [] : $aArticle['data'];
|
||
if(empty($aArticle)){
|
||
return json_encode(['status' => 3,'msg' => 'The article does not exist']);
|
||
}
|
||
|
||
//请求AI获取结果
|
||
$aResult = $this->curlOpenAIByDoi(['doi' => $aRefer['refer_doi']]);
|
||
$iStatus = empty($aResult['status']) ? 0 : $aResult['status'];
|
||
$sMsg = empty($aResult['msg']) ? 'The DOI number AI did not find any relevant information' : $aResult['msg'];
|
||
if($iStatus != 1){
|
||
return json_encode(['status' => 4,'msg' => $sMsg]);
|
||
}
|
||
$aData = empty($aResult['data']) ? [] : $aResult['data'];
|
||
if(empty($aData)){
|
||
return json_encode(['status' => 5,'msg' => 'AI obtains empty data']);
|
||
}
|
||
//写入日志
|
||
$aLog = [];
|
||
$aLog['content'] = json_encode($aResult);
|
||
$aLog['update_time'] = time();
|
||
$aLog['p_refer_id'] = $iPReferId;
|
||
$iLogId = Db::name('production_article_refer_ai')->insertGetId($aLog);
|
||
$iIsAiCheck = empty($aData['is_ai_check']) ? 2 : $aData['is_ai_check'];
|
||
if($iIsAiCheck != 1){//AI未检测到信息
|
||
return json_encode(['status' => 6,'msg' => 'AI did not find any information'.json_encode($aParam)]);
|
||
}
|
||
|
||
//数据处理入库
|
||
$aField = ['author','title','joura','dateno','doilink'];
|
||
foreach ($aField as $key => $value) {
|
||
if(empty($aData[$value])){
|
||
continue;
|
||
}
|
||
if($value == 'author'){
|
||
$aUpdate['author'] = implode(',', $aData['author']);
|
||
// $aUpdate['author'] = str_replace('et al.', '', $aUpdate['author']);
|
||
}else{
|
||
$aUpdate[$value] = $aData[$value];
|
||
}
|
||
}
|
||
if(empty($aUpdate)){
|
||
return json_encode(['status' => 6,'msg' => 'Update data to empty'.json_encode($aData)]);
|
||
}
|
||
if($aRefer['refer_type'] == 'other'){
|
||
$aUpdate['refer_type'] = 'journal';
|
||
}
|
||
if($aRefer['refer_type'] == 'book' && !empty($aUpdate['doilink'])){
|
||
$aUpdate['refer_type'] = $aUpdate['doilink'];
|
||
unset($aUpdate['doilink']);
|
||
}
|
||
$aLog = $aUpdate;
|
||
$aUpdate['is_change'] = 1;
|
||
$aUpdate['is_ai_check'] = 1;
|
||
$aUpdate['cs'] = 1;
|
||
$aUpdate['update_time'] = time();
|
||
Db::startTrans();
|
||
//更新数据
|
||
$aWhere = ['p_refer_id' => $iPReferId,'state' => 0];
|
||
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($aUpdate);
|
||
if($result === false){
|
||
return json_encode(['status' => 6,'msg' => 'Update failed']);
|
||
}
|
||
//更新日志
|
||
if(!empty($iLogId)){
|
||
$aWhere = ['id' => $iLogId];
|
||
if(isset($aLog['refer_type'])){
|
||
unset($aLog['refer_type']);
|
||
}
|
||
$result = Db::name('production_article_refer_ai')->where($aWhere)->limit(1)->update($aLog);
|
||
}
|
||
Db::commit();
|
||
return json_encode(['status' => 1,'msg' => 'success']);
|
||
}
|
||
|
||
/**
|
||
* 对接OPENAI
|
||
*/
|
||
private function curlOpenAIByDoi($aParam = []){
|
||
|
||
//获取DOI
|
||
$sDoi = empty($aParam['doi']) ? '' : $aParam['doi'];
|
||
if(empty($sDoi)){
|
||
return ['status' => 2,'msg' => 'Reference doi is empty'];
|
||
}
|
||
//系统角色
|
||
$sSysMessagePrompt = '请完成以下任务:
|
||
1. 根据提供的DOI号,查询该文献的AMA引用格式;
|
||
2. 按照以下规则调整AMA引用格式:
|
||
- 第三个作者名字后添加 et al.;
|
||
- DOI前加上"Available at: ";
|
||
- DOI信息格式调整为"https://doi.org/+真实DOI"(替换真实DOI为文献实际DOI).
|
||
3. 严格按照以下JSON结构返回结果,仅返回JSON数据,不要额外文字,包含字段:doilink(url格式)、title(标题)、author(作者数组)、joura(出版社名称)、dateno(年;卷(期):起始页-终止页),is_ai_check(默认1)
|
||
4. 若未查询到信息,字段is_ai_check为2,相关字段为null。';
|
||
//用户角色
|
||
$sUserPrompt = '我提供的DOI是:'.$sDoi;
|
||
$aMessage = [
|
||
['role' => 'system', 'content' => $sSysMessagePrompt],
|
||
['role' => 'user', 'content' => $sUserPrompt],
|
||
];
|
||
//请求OPENAI接口
|
||
$sModel = empty($aParam['model']) ? 'gpt-4.1' : $aParam['model'];//模型
|
||
$sApiUrl = $this->sApiUrl;//'http://chat.taimed.cn/v1/chat/completions';//
|
||
$aParam = ['model' => $sModel,'url' => $sApiUrl,'temperature' => 0,'messages' => $aMessage,'api_key' => $this->sApiKey];
|
||
$oOpenAi = new \app\common\OpenAi;
|
||
$aResult = json_decode($oOpenAi->curlOpenAI($aParam),true);
|
||
return $aResult;
|
||
}
|
||
/**
|
||
* 作者修改完成发邮件
|
||
*/
|
||
public function finishSendEmail(){
|
||
//获取参数
|
||
$aParam = empty($aParam) ? $this->request->post() : $aParam;
|
||
//文章ID
|
||
$iArticleId = empty($aParam['article_id']) ? '' : $aParam['article_id'];
|
||
if(empty($iArticleId)){
|
||
return json_encode(array('status' => 2,'msg' => 'Please select an article'));
|
||
}
|
||
//查询条件
|
||
$aWhere = ['article_id' => $iArticleId,'state' => ['in',[5,6]]];
|
||
$aArticle = Db::name('article')->field('article_id,journal_id,accept_sn')->where($aWhere)->find();
|
||
if(empty($aArticle)){
|
||
return json_encode(['status' => 3,'msg' => 'The article does not exist or has not entered the editorial reference status']);
|
||
}
|
||
$aWhere = ['article_id' => $iArticleId,'state' => 0];
|
||
$aProductionArticle = Db::name('production_article')->field('p_article_id')->where($aWhere)->find();
|
||
if(empty($aProductionArticle)) {
|
||
return ['status' => 2,'msg' => 'The article has not entered the production stage'];
|
||
}
|
||
//查询是否有参考文献
|
||
$aWhere = ['p_article_id' => $aProductionArticle['p_article_id'],'state' => 0];
|
||
$aRefer = Db::name('production_article_refer')->field('article_id')->where($aWhere)->find();
|
||
if(empty($aRefer)) {
|
||
return ['status' => 2,'msg' => 'No reference information found, please be patient and wait for the editor to upload'];
|
||
}
|
||
//查询期刊信息
|
||
if(empty($aArticle['journal_id'])){
|
||
return json_encode(array('status' => 4,'msg' => 'The article is not associated with a journal' ));
|
||
}
|
||
$aWhere = ['state' => 0,'journal_id' => $aArticle['journal_id']];
|
||
$aJournal = Db::name('journal')->where($aWhere)->find();
|
||
if(empty($aJournal)){
|
||
return json_encode(array('status' => 5,'msg' => 'No journal information found' ));
|
||
}
|
||
//查询编辑邮箱
|
||
$iUserId = empty($aJournal['editor_id']) ? '' : $aJournal['editor_id'];
|
||
if(empty($iUserId)){
|
||
return json_encode(array('status' => 6,'msg' => 'The journal to which the article belongs has not designated a responsible editor' ));
|
||
}
|
||
$aWhere = ['user_id' => $iUserId,'state' => 0,'email' => ['<>','']];
|
||
$aUser = Db::name('user')->field('user_id,email,realname,account')->where($aWhere)->find();
|
||
if(empty($aUser)){
|
||
return json_encode(['status' => 7,'msg' => "Edit email as empty"]);
|
||
}
|
||
|
||
//处理发邮件
|
||
//邮件模版
|
||
$aEmailConfig = [
|
||
'email_subject' => '{journal_title}-{accept_sn}',
|
||
'email_content' => '
|
||
Dear Editor,<br><br>
|
||
The authors have revised the formats of all references, please check.<br>
|
||
Sn:{accept_sn}<br><br>
|
||
Sincerely,<br>Editorial Office<br>
|
||
<a href="https://www.tmrjournals.com/draw_up.html?issn={journal_issn}">Subscribe to this journal</a><br>{journal_title}<br>
|
||
Email: {journal_email}<br>
|
||
Website: {website}'
|
||
];
|
||
//邮件内容
|
||
$aSearch = [
|
||
'{accept_sn}' => empty($aArticle['accept_sn']) ? '' : $aArticle['accept_sn'],//accept_sn
|
||
'{journal_title}' => empty($aJournal['title']) ? '' : $aJournal['title'],//期刊名
|
||
'{journal_issn}' => empty($aJournal['issn']) ? '' : $aJournal['issn'],
|
||
'{journal_email}' => empty($aJournal['email']) ? '' : $aJournal['email'],
|
||
'{website}' => empty($aJournal['website']) ? '' : $aJournal['website'],
|
||
];
|
||
|
||
//发邮件
|
||
//邮件标题
|
||
$email = $aUser['email'];
|
||
$title = str_replace(array_keys($aSearch), array_values($aSearch),$aEmailConfig['email_subject']);
|
||
//邮件内容变量替换
|
||
$content = str_replace(array_keys($aSearch), array_values($aSearch), $aEmailConfig['email_content']);
|
||
$pre = \think\Env::get('emailtemplete.pre');
|
||
$net = \think\Env::get('emailtemplete.net');
|
||
$net1 = str_replace("{{email}}",trim($email),$net);
|
||
$content=$pre.$content.$net1;
|
||
//发送邮件
|
||
$memail = empty($aJournal['email']) ? '' : $aJournal['email'];
|
||
$mpassword = empty($aJournal['epassword']) ? '' : $aJournal['epassword'];
|
||
//期刊标题
|
||
$from_name = empty($aJournal['title']) ? '' : $aJournal['title'];
|
||
//邮件队列组装参数
|
||
$aResult = sendEmail($email,$title,$from_name,$content,$memail,$mpassword);
|
||
$iStatus = empty($aResult['status']) ? 1 : $aResult['status'];
|
||
$iIsSuccess = 2;
|
||
$sMsg = empty($aResult['data']) ? '失败' : $aResult['data'];
|
||
if($iStatus == 1){
|
||
return json_encode(['status' => 1,'msg' => 'success']);
|
||
}
|
||
return json_encode(['status' => 8,'msg' => 'fail']);
|
||
}
|
||
}
|