303 lines
14 KiB
PHP
303 lines
14 KiB
PHP
<?php
|
||
namespace app\common;
|
||
use think\Db;
|
||
use think\Env;
|
||
class ProductionArticleRefer
|
||
{
|
||
|
||
// DOI匹配核心正则(生产级优化:支持%字符、限制长度、单词边界断言)
|
||
// private const DOI_PATTERN = '/(?:doi[:\s]?|DOI[:\s]?)?\b10\.\d+(?:\.\d+)*\/[a-zA-Z0-9._\-!()%\/:;@$&+=?#[\]<>~`|^]+/i';
|
||
|
||
// 错误码与错误信息映射(标准化错误处理)
|
||
private const ERROR_CODES = [
|
||
'EMPTY_STRING' => 'Input string is empty (preprocessed))',
|
||
'NO_MATCH' => 'No valid DOI detected',
|
||
'INVALID_AFTER_CLEAN' => 'No effective DOI after cleaning',
|
||
'FORCE_EXTRACT_FAILED' => 'Forced extraction still has no valid DOI',
|
||
'EXTRACTION_EXCEPTION' => 'Exception occurred during DOI extraction process',
|
||
];
|
||
|
||
/**
|
||
* 获取未处理的参考文献
|
||
*
|
||
* @return void
|
||
*/
|
||
public function top($aParam = []) {
|
||
|
||
//文章ID
|
||
$iArticleId = empty($aParam['article_id']) ? '' : $aParam['article_id'];
|
||
if(empty($iArticleId)){
|
||
return json_encode(array('status' => 2,'msg' => 'Please select an article'.json_encode($aParam) ));
|
||
}
|
||
// 获取生产文章ID
|
||
$iPArticleId = empty($aParam['p_article_id']) ? 0 : $aParam['p_article_id'];
|
||
if(empty($iPArticleId)) {
|
||
return json_encode(array('status' => 2,'msg' => 'Please select an production article'.json_encode($aParam) ));
|
||
}
|
||
|
||
//查询未处理过的数据
|
||
$aWhere = ['p_article_id' => $iPArticleId,'article_id' => $iArticleId,'state' => 0,'refer_doi' => ['<>',''],'is_deal' => 2];
|
||
$aResult = Db::name('production_article_refer')->field('article_id,p_article_id,p_refer_id,refer_doi')->where($aWhere)->select();
|
||
if(empty($aResult)){
|
||
return json_encode(array('status' => 2,'msg' => 'The reference data to be processed is empty'.json_encode($aParam)));
|
||
}
|
||
|
||
//数据处理
|
||
foreach ($aResult as $key => $value) {
|
||
if(empty($value['refer_doi'])){
|
||
continue;
|
||
}
|
||
//调用获取参考文献详情队列
|
||
\think\Queue::push('app\api\job\ArticleReferDetailQueue@fire', $value, 'ArticleReferDetailQueue');
|
||
}
|
||
return json_encode(['status' => 1,'msg' => 'Add to reference processing queue']);
|
||
}
|
||
/**
|
||
* 处理参考文献
|
||
*
|
||
* @return void
|
||
*/
|
||
public function get($aParam = []) {
|
||
// 获取生产文章ID
|
||
$iPReferId = empty($aParam['p_refer_id']) ? 0 : $aParam['p_refer_id'];
|
||
if(empty($iPReferId)) {
|
||
return json_encode(array('status' => 2,'msg' => 'Please select a reference'.json_encode($aParam) ));
|
||
}
|
||
// 获取生产文章ID
|
||
$iPArticleId = empty($aParam['p_article_id']) ? 0 : $aParam['p_article_id'];
|
||
if(empty($iPArticleId)) {
|
||
return json_encode(array('status' => 2,'msg' => 'Please select an production article'.json_encode($aParam) ));
|
||
}
|
||
//查询未处理过的数据
|
||
$aWhere = ['p_refer_id' => $iPReferId,'p_article_id' => $iPArticleId,'state' => 0];
|
||
$aRefer = Db::name('production_article_refer')->field('refer_doi,refer_content')->where($aWhere)->find();
|
||
if(empty($aRefer)){
|
||
return json_encode(array('status' => 2,'msg' => 'No reference records found'.json_encode($aParam)));
|
||
}
|
||
if(empty($aRefer['refer_doi'])){
|
||
return json_encode(['status' => 4,'msg' => 'Reference DOI is empty'.json_encode($aParam)]);
|
||
}
|
||
|
||
//数据处理
|
||
$doi = str_replace('/', '%2F', $aRefer['refer_doi']);
|
||
$url = "https://citation.doi.org/format?doi=$doi&style=cancer-translational-medicine&lang=en-US";
|
||
$res = myGet($url);
|
||
$frag = trim(substr($res, strpos($res, '.') + 1));
|
||
if(empty($frag)){
|
||
$aUpdate = ['refer_frag' => $aRefer['refer_content'],'refer_type' => 'other','is_deal' => 1,'update_time' => time()];
|
||
$aWhere = ['p_refer_id' => $iPReferId];
|
||
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($aUpdate);
|
||
//写入通过AI获取参考文献详情队列
|
||
// \think\Queue::push('app\api\job\AiCheckReferByDoi@fire',$aParam,'AiCheckReferByDoi');
|
||
return json_encode(array('status' => 2,'msg' => 'The data obtained from the interface is empty'.$url));
|
||
}
|
||
|
||
//整理数据入库
|
||
$update = [];
|
||
if (mb_substr_count($frag, '.') != 3){
|
||
$f = $frag . " Available at: " . PHP_EOL . "https://doi.org/" . $aRefer['refer_doi'];
|
||
$update['refer_type'] = "other";
|
||
$update['refer_frag'] = $f;
|
||
$update['cs'] = 1;
|
||
//写入通过AI获取参考文献详情队列
|
||
// \think\Queue::push('app\api\job\AiCheckReferByDoi@fire',$aParam,'AiCheckReferByDoi');
|
||
}
|
||
if (mb_substr_count($frag, '.') == 3){
|
||
$res = explode('.', $frag);
|
||
$update['author'] = prgeAuthor($res[0]);
|
||
$update['title'] = trim($res[1]);
|
||
$bj = bekjournal($res[2]);
|
||
$joura = formateJournal(trim($bj[0]));
|
||
$update['joura'] = $joura;
|
||
$is_js = 0;
|
||
if ($joura == trim($bj[0])) {
|
||
}
|
||
$update['refer_type'] = "journal";
|
||
$update['is_ja'] = $joura == trim($bj[0]) ? 0 : 1;
|
||
$update['dateno'] = str_replace(' ', '', str_replace('-', '–', trim($bj[1])));
|
||
//新增处理 期卷页码 20251127 start
|
||
if(!empty($update['dateno'])){
|
||
$sStr = $update['dateno'];
|
||
$aStr = explode(':', $sStr);
|
||
if(!empty($aStr[1])){
|
||
$parts = explode('–', $aStr[1]);
|
||
if(count($parts) == 2){
|
||
$prefix = empty($parts[0]) ? 0 : intval($parts[0]);
|
||
$suffix = empty($parts[1]) ? 0 : intval($parts[1]);
|
||
if($prefix > $suffix){
|
||
$prefixLen = strlen($prefix);
|
||
$suffixLen = strlen($suffix);
|
||
$missingLen = $prefixLen - $suffixLen;
|
||
if ($missingLen > 0) {
|
||
$fillPart = substr($prefix, 0, $missingLen);
|
||
$newSuffix = $fillPart . $suffix;
|
||
$update['dateno'] = $aStr[0].':'.$prefix.'–'.$newSuffix;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// if(empty($aStr[1])){
|
||
// //写入通过AI获取参考文献详情队列
|
||
// \think\Queue::push('app\api\job\AiCheckReferByDoi@fire',$aParam,'AiCheckReferByDoi');
|
||
// }
|
||
}
|
||
//新增处理 期卷页码 20251127 end
|
||
$update['doilink'] = strpos($aRefer['refer_doi'],"http")===false?"https://doi.org/" . $aRefer['refer_doi']:$aRefer['refer_doi'];
|
||
$update['cs'] = 1;
|
||
}
|
||
//数据库更新
|
||
if(empty($update)){
|
||
return json_encode(array('status' => 3,'msg' => 'Update data to empty'.$url.'====='.$frag));
|
||
}
|
||
$aWhere = ['p_refer_id' => $iPReferId];
|
||
$update += ['is_deal' => 1,'update_time' => time()];
|
||
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($update);
|
||
if($result === false){
|
||
return json_encode(array('status' => 3,'msg' => 'Update failed'.json_encode($update)));
|
||
}
|
||
return json_encode(['status' => 1,'msg' => 'Update successful']);
|
||
}
|
||
|
||
// /**
|
||
// * 实例方法:提取单个DOI(核心逻辑,生产级优化)
|
||
// * @param string $str 待检测字符串
|
||
// * @param bool $standardize 是否标准化DOI(转小写)
|
||
// * @param bool $forceExtract 是否强制提取(忽略微小格式瑕疵)
|
||
// * @return array 提取结果(含错误码、错误信息、DOI)
|
||
// */
|
||
// // public function extractDoiFromString(string $str, bool $standardize = true, bool $forceExtract = false): array
|
||
// // {
|
||
// // // 初始化标准化结果
|
||
// // $result = [
|
||
// // 'has_doi' => false,
|
||
// // 'doi' => null,
|
||
// // 'error_code' => null,
|
||
// // 'error_msg' => null,
|
||
// // ];
|
||
|
||
// // try {
|
||
// // // 严格类型校验(防止非字符串参数传入)
|
||
// // if (!is_string($str)) {
|
||
// // throw new InvalidArgumentException('输入参数必须为字符串类型', 1001);
|
||
// // }
|
||
// // // 字符串预处理(生产级:全角转半角、URL解码、HTML标签移除等)
|
||
// // $processedStr = $this->preprocessString($str);
|
||
// // if (trim($processedStr) === '') {
|
||
// // $result['error_code'] = 'EMPTY_STRING';
|
||
// // $result['error_msg'] = self::ERROR_CODES['EMPTY_STRING'];
|
||
// // return $result;
|
||
// // }
|
||
|
||
// // // 性能优化:用preg_match仅匹配首个DOI,替代preg_match_all
|
||
// // // 优化后的带前缀版正则
|
||
// // $pattern = '/(?:doi[:\s]*|DOI[:\s]*)?\b10\.\d+(?:\.\d+)*\/[a-zA-Z0-9._\-!()%\/:;@$&+=?#[\]<>~`|^'"{},\\\\]+(?![\w?#])/i";
|
||
// // if (!preg_match($pattern, $processedStr, $match)) {
|
||
// // $result['error_code'] = 'NO_MATCH';
|
||
// // $result['error_msg'] = self::ERROR_CODES['NO_MATCH'];
|
||
// // return $result;
|
||
// // }
|
||
|
||
// // // 清洗并验证首个DOI
|
||
// // $cleanDoi = $this->cleanAndValidateDoi($match[0], $standardize, $forceExtract);
|
||
// // if ($cleanDoi !== null) {
|
||
// // $result['has_doi'] = true;
|
||
// // $result['doi'] = $cleanDoi;
|
||
// // } else {
|
||
// // // 根据是否强制提取设置错误信息
|
||
// // $errorKey = $forceExtract ? 'FORCE_EXTRACT_FAILED' : 'INVALID_AFTER_CLEAN';
|
||
// // $result['error_code'] = $errorKey;
|
||
// // $result['error_msg'] = self::ERROR_CODES[$errorKey];
|
||
// // }
|
||
|
||
// // } catch (InvalidArgumentException $e) {
|
||
// // // 业务异常:标准化错误码和信息
|
||
// // $result['error_code'] = 'INVALID_PARAM';
|
||
// // $result['error_msg'] = '参数错误:' . $e->getMessage();
|
||
// // } catch (Exception $e) {
|
||
// // // 系统异常:隐藏敏感信息,记录通用错误
|
||
// // $result['error_code'] = 'EXTRACTION_EXCEPTION';
|
||
// // $result['error_msg'] = self::ERROR_CODES['EXTRACTION_EXCEPTION'] . ':' . $e->getMessage();
|
||
// // }
|
||
|
||
// // return $result;
|
||
// // }
|
||
|
||
// // /**
|
||
// // * 字符串预处理(生产级:覆盖所有编码/格式干扰场景)
|
||
// // * @param string $str 原始字符串
|
||
// // * @return string 预处理后的纯净字符串
|
||
// // */
|
||
// // private function preprocessString(string $str): string
|
||
// // {
|
||
// // // 1. 全角转半角(解决中文全角字符干扰,如10.1007/s11042-020-10103-4)
|
||
// // $str = $this->fullWidthToHalfWidth($str);
|
||
// // // 2. 移除所有HTML标签(解决网页文本中DOI被<p>/<a>/<b>等标签包裹的问题)
|
||
// // $str = strip_tags($str);
|
||
// // // 3. URL解码(处理%2F等URL编码的特殊字符,如10.1007%2Fs11042-020-10103-4)
|
||
// // $str = urldecode($str);
|
||
// // // 4. 解码HTML实体(处理&、/等HTML实体编码)
|
||
// // $str = html_entity_decode($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
|
||
// // // 5. 移除不可见字符(换行、制表符、零宽空格、控制字符等)
|
||
// // $str = preg_replace('/[\x00-\x1F\x7F\x{200B}-\x{200F}]/u', ' ', $str);
|
||
// // // 6. 合并多个空格为单个(避免连续空格干扰正则匹配)
|
||
// // $str = preg_replace('/\s+/', ' ', $str);
|
||
|
||
// // return $str;
|
||
// // }
|
||
|
||
// // /**
|
||
// // * 清洗并验证DOI(生产级:优化正则规则,严格长度校验)
|
||
// // * @param string $match 原始正则匹配结果
|
||
// // * @param bool $standardize 是否标准化DOI(转小写)
|
||
// // * @param bool $forceExtract 是否强制提取
|
||
// // * @return string|null 有效DOI或null
|
||
// // */
|
||
// // private function cleanAndValidateDoi(string $match, bool $standardize, bool $forceExtract): ?string
|
||
// // {
|
||
// // // 1. 移除DOI前缀(doi:/DOI:)和首尾空白字符
|
||
// // $cleanDoi = preg_replace('/^doi[:\s]?|^DOI[:\s]?/i', '', trim($match));
|
||
// // // 2. 移除尾部常见标点(避免DOI被标点包裹,如10.1007/s11042-020-10103-4.)
|
||
// // $cleanDoi = rtrim($cleanDoi, '.,;(){}[]!?"\'');
|
||
|
||
// // // 3. 严格的长度校验(DOI官方规范:6-200字符)
|
||
// // $doiLength = strlen($cleanDoi);
|
||
// // if ($doiLength < 6 || $doiLength > 200) {
|
||
// // return null;
|
||
// // }
|
||
|
||
// // // 4. 验证规则(生产级优化:添加单词边界,避免匹配不完整DOI)
|
||
// // // 基础规则:严格遵循官方规范,10.开头+包含/+/后有内容
|
||
// // $basicRule = '/^10\.\d+\/.+$/D';
|
||
// // // 宽松规则:强制提取时使用,添加单词边界,避免匹配被字符包裹的DOI
|
||
// // $looseRule = '/\b10\.\d+\/[^\s%]{1,190}\b/';
|
||
|
||
// // $validateRule = $forceExtract ? $looseRule : $basicRule;
|
||
// // $isValid = preg_match($validateRule, $cleanDoi) === 1;
|
||
|
||
// // // 5. 验证通过则标准化(转小写),否则返回null
|
||
// // if ($isValid) {
|
||
// // return $standardize ? strtolower($cleanDoi) : $cleanDoi;
|
||
// // }
|
||
|
||
// // return null;
|
||
// // }
|
||
|
||
// // /**
|
||
// // * 辅助方法:全角转半角
|
||
// // * @param string $str 包含全角字符的字符串
|
||
// // * @return string 半角字符串
|
||
// // */
|
||
// // private function fullWidthToHalfWidth(string $str): string
|
||
// // {
|
||
// // $fullWidthChars = [
|
||
// // '0' => '0', '1' => '1', '2' => '2', '3' => '3', '4' => '4',
|
||
// // '5' => '5', '6' => '6', '7' => '7', '8' => '8', '9' => '9',
|
||
// // '.' => '.', '/' => '/', '-' => '-', '%' => '%', '!' => '!',
|
||
// // '(' => '(', ')' => ')', ':' => ':', ';' => ';', ',' => ',',
|
||
// // '"' => '"', ''' => '\''
|
||
// // ];
|
||
|
||
// // return strtr($str, $fullWidthChars);
|
||
// // }
|
||
}
|
||
?>
|