Files
tougao/application/common/ReferenceDispatchService.php
2026-07-09 15:42:55 +08:00

252 lines
8.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common;
use think\Db;
use think\Env;
use think\Queue;
/**
* 参考文献分流处理freshRefers 入口之后,按 journal / book / other 三路处理。
*
* - journal有 DOI 入 ArticleReferDetailQueue无 DOI 保留原文
* - book填充结构化字段 author/title/joura/dateno/isbn与 Preaccept、References 约定一致)
* - otherrefer_frag = refer_content不做结构化
*/
class ReferenceDispatchService
{
/** @var ReferenceTypeClassifier */
private $classifier;
public function __construct()
{
$this->classifier = new ReferenceTypeClassifier(['use_llm' => true]);
}
/**
* 对某篇生产文章的全部参考文献按类型分流处理
*/
public function dispatchRefersByType($pArticleId)
{
$pArticleId = intval($pArticleId);
if ($pArticleId <= 0) {
return;
}
$refers = Db::name('production_article_refer')
->where('p_article_id', $pArticleId)
->where('state', 0)
->order('index asc, p_refer_id asc')
->select();
if (empty($refers)) {
return;
}
$crossref = new CrossrefService([
'mailto' => trim((string)Env::get('crossref_mailto', '')),
]);
foreach ($refers as $refer) {
$summary = null;
$crossrefType = '';
if (trim((string)$refer['refer_doi']) !== '') {
$doiNorm = $this->normalizeDoi($refer['refer_doi']);
if ($doiNorm !== '') {
$summary = $crossref->fetchWorkSummary($doiNorm);
if ($summary && !empty($summary['raw']['type'])) {
$crossrefType = (string)$summary['raw']['type'];
}
}
}
$typeInfo = $this->classifier->classify((string)$refer['refer_content'], $crossrefType);
$dispatchType = $this->classifier->normalizeDispatchType($typeInfo['type']);
Db::name('production_article_refer')->where('p_refer_id', $refer['p_refer_id'])->update([
'refer_type' => $dispatchType,
'update_time' => time(),
]);
$refer['refer_type'] = $dispatchType;
switch ($dispatchType) {
case ReferenceTypeClassifier::TYPE_BOOK:
$this->processBookRefer($refer, $summary, $crossref);
break;
case ReferenceTypeClassifier::TYPE_OTHER:
$this->processOtherRefer($refer);
break;
default:
$this->processJournalRefer($refer);
break;
}
}
}
private function processJournalRefer(array $refer)
{
$pReferId = intval($refer['p_refer_id']);
if (trim((string)$refer['refer_doi']) === '') {
Db::name('production_article_refer')->where('p_refer_id', $pReferId)->update([
'refer_frag' => $refer['refer_content'],
'refer_type' => ReferenceTypeClassifier::TYPE_JOURNAL,
'is_deal' => 1,
'update_time' => time(),
]);
return;
}
Queue::push('app\api\job\ArticleReferDetailQueue@fire', $refer, 'ArticleReferDetailQueue');
}
private function processOtherRefer(array $refer)
{
Db::name('production_article_refer')->where('p_refer_id', intval($refer['p_refer_id']))->update([
'refer_frag' => $refer['refer_content'],
'refer_type' => ReferenceTypeClassifier::TYPE_OTHER,
'cs' => 0,
'is_deal' => 1,
'update_time' => time(),
]);
}
/**
* book结构化字段DOI 仅用于补数据
*/
private function processBookRefer(array $refer, $summary, CrossrefService $crossref)
{
$pReferId = intval($refer['p_refer_id']);
$content = (string)$refer['refer_content'];
$update = [
'refer_type' => ReferenceTypeClassifier::TYPE_BOOK,
'is_deal' => 1,
'update_time' => time(),
];
if (is_array($summary) && !empty($summary['raw'])) {
$raw = $summary['raw'];
$authorCitation = $crossref->getAuthorsCitation($raw, 3);
$update['author'] = $authorCitation !== '' ? rtrim($authorCitation, '.') . '.' : '';
$update['title'] = trim((string)($summary['title'] ?? ''));
$update['joura'] = $this->extractBookPublisher($raw, $summary);
$update['dateno'] = $this->extractBookDateno($raw);
$isbn = $this->extractIsbnFromRaw($raw);
if ($isbn === '' && !empty($refer['refer_doi'])) {
$doi = $this->normalizeDoi($refer['refer_doi']);
$isbn = $doi !== '' ? 'https://doi.org/' . $doi : '';
}
$update['isbn'] = $isbn;
$update['is_ja'] = 1;
} else {
$parsed = $this->parseBookFromContent($content);
$update = array_merge($update, $parsed);
}
$hasCore = trim((string)($update['author'] ?? '')) !== ''
&& trim((string)($update['title'] ?? '')) !== '';
$update['cs'] = $hasCore ? 1 : 0;
if (!$hasCore) {
$update['refer_frag'] = $content;
}
Db::name('production_article_refer')->where('p_refer_id', $pReferId)->update($update);
}
private function extractBookPublisher(array $raw, array $summary)
{
$publisher = trim((string)($raw['publisher'] ?? ''));
if ($publisher !== '') {
return $publisher;
}
$pub = $summary['publisher'] ?? [];
if (!empty($pub['publisher'])) {
return trim((string)$pub['publisher']);
}
if (!empty($pub['title'])) {
return trim((string)$pub['title']);
}
return '';
}
private function extractBookDateno(array $raw)
{
if (!empty($raw['published']['date-parts'][0][0])) {
return (string)$raw['published']['date-parts'][0][0];
}
if (!empty($raw['issued']['date-parts'][0][0])) {
return (string)$raw['issued']['date-parts'][0][0];
}
if (!empty($raw['created']['date-parts'][0][0])) {
return (string)$raw['created']['date-parts'][0][0];
}
return '';
}
private function extractIsbnFromRaw(array $raw)
{
if (empty($raw['ISBN']) || !is_array($raw['ISBN'])) {
return '';
}
foreach ($raw['ISBN'] as $isbn) {
$isbn = trim((string)$isbn);
if ($isbn !== '') {
return $isbn;
}
}
return '';
}
/**
* 无 Crossref 时从原文尽量抽取 book 结构化字段
*/
private function parseBookFromContent($content)
{
$content = trim((string)$content);
$out = [
'author' => '',
'title' => '',
'joura' => '',
'dateno' => '',
'isbn' => '',
'is_ja' => 1,
];
if ($content === '') {
return $out;
}
if (preg_match('/\bISBN[:\s]*([\d\-Xx\s]+)/i', $content, $m)) {
$out['isbn'] = preg_replace('/\s+/', '-', trim($m[1]));
}
if (preg_match('/\b(19|20)\d{2}\b/', $content, $m)) {
$out['dateno'] = $m[0];
}
// Place: Publisher; Year → joura 取 Publisher
if (preg_match('/:\s*([^;]+);\s*(19|20)\d{2}/', $content, $m)) {
$out['joura'] = trim($m[1]);
} elseif (preg_match('/\b([A-Z][A-Za-z .&]+(?:Press|Publishing|Publisher|Books?))\b/i', $content, $m)) {
$out['joura'] = trim($m[1]);
}
// 作者. 标题. ... 简单拆分
$parts = preg_split('/\.\s+/', $content, 3);
if (is_array($parts) && count($parts) >= 2) {
$out['author'] = trim($parts[0]);
if (substr($out['author'], -1) !== '.') {
$out['author'] .= '.';
}
$out['title'] = trim(rtrim($parts[1], '.'));
}
return $out;
}
private function normalizeDoi($doi)
{
$doi = preg_replace('#^https?://(dx\.)?doi\.org/#i', '', trim((string)$doi));
return trim($doi, " \t\n\r\0\x0B/");
}
}