大更新,1投稿系统参考文献的类型增加功能,,,2官网数据库功能
This commit is contained in:
@@ -172,6 +172,125 @@ class Order extends base{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**提交改价申请
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function applyPrice(){
|
||||||
|
$data = $this->request->post();
|
||||||
|
$rule = new Validate([
|
||||||
|
"article_id"=>"require",
|
||||||
|
"fee"=>"require",
|
||||||
|
"remark"=>"require"
|
||||||
|
]);
|
||||||
|
if(!$rule->check($data)){
|
||||||
|
return jsonError($rule->getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询文章、期刊和编辑者信息
|
||||||
|
$article_info = $this->article_obj->where("article_id",$data['article_id'])->find();
|
||||||
|
$journal_info = $this->journal_obj->where("journal_id",$article_info['journal_id'])->find();
|
||||||
|
$editor_info = $this->user_obj->where("user_id",$journal_info['editor_id'])->find();
|
||||||
|
|
||||||
|
// 验证编辑者权限
|
||||||
|
if($journal_info['editor_id']!=$editor_info['user_id']){
|
||||||
|
return jsonError("You are not the editor of this journal");
|
||||||
|
}
|
||||||
|
|
||||||
|
$check = Db::name("article_price_apply")->where("article_id",$data['article_id'])->where("state",0)->find();
|
||||||
|
if($check){
|
||||||
|
return jsonError("There is an unfinished price change application for this article");
|
||||||
|
}
|
||||||
|
|
||||||
|
$insert['article_id'] = $data['article_id'];
|
||||||
|
$insert['old_fee'] = $article_info['fee'];
|
||||||
|
$insert['fee'] = $data['fee'];
|
||||||
|
$insert['remark'] = $data['remark'];
|
||||||
|
$insert['ctime'] = time();
|
||||||
|
Db::name("article_price_apply")->insert($insert);
|
||||||
|
|
||||||
|
return jsonSuccess();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getApplyList(){
|
||||||
|
$list = Db::name("article_price_apply")->where("state",0)->select();
|
||||||
|
foreach ($list as $k=>$v){
|
||||||
|
$list[$k]['article_info'] = $this->article_obj->field("article_id,journal_id,accept_sn,title")->where("article_id",$v['article_id'])->find();
|
||||||
|
$list[$k]['journal_info'] = $this->journal_obj->field("journal_id,title")->where("journal_id",$list[$k]['article_info']['journal_id'])->find();
|
||||||
|
$list[$k]['history'] = Db::name("article_price_apply")->where("article_id",$list[$k]['article_info']['article_id'])->select();
|
||||||
|
}
|
||||||
|
|
||||||
|
$re['list'] = $list;
|
||||||
|
|
||||||
|
return jsonSuccess($re);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function acceptPriceApply(){
|
||||||
|
$data = $this->request->post();
|
||||||
|
$rule = new Validate([
|
||||||
|
"id"=>"require"
|
||||||
|
]);
|
||||||
|
if(!$rule->check($data)){
|
||||||
|
return jsonError($rule->getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
$apply_info = Db::name("article_price_apply")->where("id",$data['id'])->find();
|
||||||
|
$article_update['fee']=$apply_info['fee'];
|
||||||
|
$article_update['fee_remark']=$apply_info['remark'];
|
||||||
|
|
||||||
|
if(intval($apply_info['fee'])==0) {
|
||||||
|
$article_update['is_buy']=1;
|
||||||
|
}
|
||||||
|
$this->article_obj->where("article_id",$apply_info['article_id'])->update($article_update);
|
||||||
|
|
||||||
|
Db::name("article_price_apply")->where("id",$data['id'])->update(['state'=>1]);
|
||||||
|
|
||||||
|
$order_info = $this->order_obj->where("article_id",$apply_info['article_id'])->whereIn("state",[0,1])->find();
|
||||||
|
if($order_info){
|
||||||
|
if(intval($apply_info['fee'])==0){
|
||||||
|
$this->order_obj->where("order_id",$order_info['order_id'])->update(['state'=>2]);
|
||||||
|
}else{
|
||||||
|
$update1['order_fee']=$apply_info['fee'];
|
||||||
|
$this->order_obj->where("order_id",$order_info['order_id'])->update($update1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return jsonSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getArticlePriceApplyList(){
|
||||||
|
$data = $this->request->post();
|
||||||
|
$rule = new Validate([
|
||||||
|
"article_id"=>"require"
|
||||||
|
]);
|
||||||
|
if(!$rule->check($data)){
|
||||||
|
return jsonError($rule->getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
$list = Db::name("article_price_apply")->where("article_id",$data['article_id'])->select();
|
||||||
|
|
||||||
|
return jsonSuccess($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function rejectPriceApply(){
|
||||||
|
$data = $this->request->post();
|
||||||
|
$rule = new Validate([
|
||||||
|
"id"=>"require",
|
||||||
|
"remark"=>"require"
|
||||||
|
]);
|
||||||
|
if(!$rule->check($data)){
|
||||||
|
return jsonError($rule->getError());
|
||||||
|
}
|
||||||
|
// $apply_info = Db::name("article_price_apply")->where("id",$data['id'])->find();
|
||||||
|
Db::name("article_price_apply")->where("id",$data['id'])->update(['state'=>2,"remark"=>trim($data['remark'])]);
|
||||||
|
return jsonSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改文章价格
|
* 修改文章价格
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -105,7 +105,10 @@ class ProductionArticleRefer
|
|||||||
$update_a['author'] = $authorCitation !== '' ? $authorCitation . '.' : '';
|
$update_a['author'] = $authorCitation !== '' ? $authorCitation . '.' : '';
|
||||||
$update_a['joura'] = $jouraRaw;
|
$update_a['joura'] = $jouraRaw;
|
||||||
$update_a['dateno'] = $dateno;
|
$update_a['dateno'] = $dateno;
|
||||||
$update_a['refer_type'] = "journal";
|
// CrossRef 的 type 最权威,据此确定参考文献类型,未命中回退 journal
|
||||||
|
$crossrefType = isset($summary['raw']['type']) ? $summary['raw']['type'] : '';
|
||||||
|
$mappedType = (new ReferenceTypeClassifier())->mapCrossrefType($crossrefType);
|
||||||
|
$update_a['refer_type'] = $mappedType !== '' ? $mappedType : "journal";
|
||||||
$update_a['is_ja'] = 1;
|
$update_a['is_ja'] = 1;
|
||||||
$update_a['doilink'] = $doilink;
|
$update_a['doilink'] = $doilink;
|
||||||
$update_a['cs'] = 1;
|
$update_a['cs'] = 1;
|
||||||
@@ -126,7 +129,13 @@ class ProductionArticleRefer
|
|||||||
$res = myGet($url);
|
$res = myGet($url);
|
||||||
$frag = trim(substr($res, strpos($res, '.') + 1));
|
$frag = trim(substr($res, strpos($res, '.') + 1));
|
||||||
if(empty($frag)){
|
if(empty($frag)){
|
||||||
$aUpdate = ['refer_frag' => $aRefer['refer_content'],'refer_type' => 'other','is_deal' => 1,'update_time' => time()];
|
// 依据作者原文识别类型;仅在识别出具体非期刊类型时覆盖,否则保持 other
|
||||||
|
$referType = 'other';
|
||||||
|
$typeInfo = (new ReferenceTypeClassifier())->classify((string)$aRefer['refer_content']);
|
||||||
|
if (in_array($typeInfo['type'], ['book','conference','thesis','web'], true)) {
|
||||||
|
$referType = $typeInfo['type'];
|
||||||
|
}
|
||||||
|
$aUpdate = ['refer_frag' => $aRefer['refer_content'],'refer_type' => $referType,'is_deal' => 1,'update_time' => time()];
|
||||||
$aWhere = ['p_refer_id' => $iPReferId];
|
$aWhere = ['p_refer_id' => $iPReferId];
|
||||||
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($aUpdate);
|
$result = Db::name('production_article_refer')->where($aWhere)->limit(1)->update($aUpdate);
|
||||||
//写入通过AI获取参考文献详情队列
|
//写入通过AI获取参考文献详情队列
|
||||||
@@ -139,6 +148,11 @@ class ProductionArticleRefer
|
|||||||
if (mb_substr_count($frag, '.') != 3){
|
if (mb_substr_count($frag, '.') != 3){
|
||||||
$f = $frag . " Available at: " . PHP_EOL . "https://doi.org/" . $aRefer['refer_doi'];
|
$f = $frag . " Available at: " . PHP_EOL . "https://doi.org/" . $aRefer['refer_doi'];
|
||||||
$update['refer_type'] = "other";
|
$update['refer_type'] = "other";
|
||||||
|
// 依据作者原文识别类型;仅在识别出具体非期刊类型时覆盖
|
||||||
|
$typeInfo = (new ReferenceTypeClassifier())->classify((string)$aRefer['refer_content']);
|
||||||
|
if (in_array($typeInfo['type'], ['book','conference','thesis','web'], true)) {
|
||||||
|
$update['refer_type'] = $typeInfo['type'];
|
||||||
|
}
|
||||||
$update['refer_frag'] = $f;
|
$update['refer_frag'] = $f;
|
||||||
$update['cs'] = 1;
|
$update['cs'] = 1;
|
||||||
//写入通过AI获取参考文献详情队列
|
//写入通过AI获取参考文献详情队列
|
||||||
@@ -155,6 +169,11 @@ class ProductionArticleRefer
|
|||||||
if ($joura == trim($bj[0])) {
|
if ($joura == trim($bj[0])) {
|
||||||
}
|
}
|
||||||
$update['refer_type'] = "journal";
|
$update['refer_type'] = "journal";
|
||||||
|
// 依据作者原文的强特征识别更精确的类型(禁用 LLM,仅规则强命中才覆盖)
|
||||||
|
$ruleType = (new ReferenceTypeClassifier(['use_llm' => false]))->classifyByRule((string)$aRefer['refer_content']);
|
||||||
|
if ($ruleType['confidence'] >= 0.8 && !in_array($ruleType['type'], ['journal','other'], true)) {
|
||||||
|
$update['refer_type'] = $ruleType['type'];
|
||||||
|
}
|
||||||
$update['is_ja'] = $joura == trim($bj[0]) ? 0 : 1;
|
$update['is_ja'] = $joura == trim($bj[0]) ? 0 : 1;
|
||||||
$update['dateno'] = str_replace(' ', '', str_replace('-', '–', trim($bj[1])));
|
$update['dateno'] = str_replace(' ', '', str_replace('-', '–', trim($bj[1])));
|
||||||
//新增处理 期卷页码 20251127 start
|
//新增处理 期卷页码 20251127 start
|
||||||
|
|||||||
221
application/common/ReferenceTypeClassifier.php
Normal file
221
application/common/ReferenceTypeClassifier.php
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common;
|
||||||
|
|
||||||
|
use app\common\service\LLMService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参考文献类型识别器
|
||||||
|
*
|
||||||
|
* 识别顺序(分层,从权威到兜底):
|
||||||
|
* 1. CrossRef 的 type 字段(若有 DOI 且命中)——最权威
|
||||||
|
* 2. 英文规则启发式(基于作者原文特征词/结构)
|
||||||
|
* 3. 大模型兜底(规则判为 other 或低置信度时)
|
||||||
|
* 4. 仍无法判定 → other
|
||||||
|
*
|
||||||
|
* 支持类型:journal / book / conference / thesis / web / other
|
||||||
|
*/
|
||||||
|
class ReferenceTypeClassifier
|
||||||
|
{
|
||||||
|
const TYPE_JOURNAL = 'journal';
|
||||||
|
const TYPE_BOOK = 'book';
|
||||||
|
const TYPE_CONFERENCE = 'conference';
|
||||||
|
const TYPE_THESIS = 'thesis';
|
||||||
|
const TYPE_WEB = 'web';
|
||||||
|
const TYPE_OTHER = 'other';
|
||||||
|
|
||||||
|
/** 是否允许使用大模型兜底 */
|
||||||
|
private $useLlm = true;
|
||||||
|
|
||||||
|
public function __construct($config = [])
|
||||||
|
{
|
||||||
|
if (is_array($config) && isset($config['use_llm'])) {
|
||||||
|
$this->useLlm = (bool)$config['use_llm'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主入口:根据作者原文(可选 CrossRef type)识别类型
|
||||||
|
*
|
||||||
|
* @param string $referContent 作者提供的原始参考文献字符串
|
||||||
|
* @param string $crossrefType CrossRef 返回的 type(可选)
|
||||||
|
* @return array ['type' => string, 'confidence' => float, 'source' => string]
|
||||||
|
*/
|
||||||
|
public function classify($referContent, $crossrefType = '')
|
||||||
|
{
|
||||||
|
$text = trim((string)$referContent);
|
||||||
|
|
||||||
|
// 1. CrossRef type 优先
|
||||||
|
$byCrossref = $this->mapCrossrefType($crossrefType);
|
||||||
|
if ($byCrossref !== '') {
|
||||||
|
return ['type' => $byCrossref, 'confidence' => 0.95, 'source' => 'crossref'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($text === '') {
|
||||||
|
return ['type' => self::TYPE_OTHER, 'confidence' => 0.0, 'source' => 'fallback'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 规则启发式
|
||||||
|
$byRule = $this->classifyByRule($text);
|
||||||
|
if ($byRule['type'] !== self::TYPE_OTHER && $byRule['confidence'] >= 0.8) {
|
||||||
|
return ['type' => $byRule['type'], 'confidence' => $byRule['confidence'], 'source' => 'rule'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 大模型兜底
|
||||||
|
if ($this->useLlm) {
|
||||||
|
$byLlm = $this->classifyByLlm($text);
|
||||||
|
if ($byLlm !== '') {
|
||||||
|
return ['type' => $byLlm, 'confidence' => 0.7, 'source' => 'llm'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 规则给出的弱结果(若有)优先于纯兜底
|
||||||
|
if ($byRule['type'] !== self::TYPE_OTHER) {
|
||||||
|
return ['type' => $byRule['type'], 'confidence' => $byRule['confidence'], 'source' => 'rule'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 兜底
|
||||||
|
return ['type' => self::TYPE_OTHER, 'confidence' => 0.0, 'source' => 'fallback'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CrossRef type → 内部枚举映射;未命中返回空串
|
||||||
|
*/
|
||||||
|
public function mapCrossrefType($crossrefType)
|
||||||
|
{
|
||||||
|
$t = strtolower(trim((string)$crossrefType));
|
||||||
|
if ($t === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = [
|
||||||
|
// 期刊
|
||||||
|
'journal-article' => self::TYPE_JOURNAL,
|
||||||
|
'journal' => self::TYPE_JOURNAL,
|
||||||
|
'journal-volume' => self::TYPE_JOURNAL,
|
||||||
|
'journal-issue' => self::TYPE_JOURNAL,
|
||||||
|
// 图书
|
||||||
|
'book' => self::TYPE_BOOK,
|
||||||
|
'book-chapter' => self::TYPE_BOOK,
|
||||||
|
'book-part' => self::TYPE_BOOK,
|
||||||
|
'book-section' => self::TYPE_BOOK,
|
||||||
|
'book-set' => self::TYPE_BOOK,
|
||||||
|
'book-series' => self::TYPE_BOOK,
|
||||||
|
'book-track' => self::TYPE_BOOK,
|
||||||
|
'reference-book' => self::TYPE_BOOK,
|
||||||
|
'edited-book' => self::TYPE_BOOK,
|
||||||
|
'monograph' => self::TYPE_BOOK,
|
||||||
|
// 会议
|
||||||
|
'proceedings-article' => self::TYPE_CONFERENCE,
|
||||||
|
'proceedings' => self::TYPE_CONFERENCE,
|
||||||
|
'proceedings-series' => self::TYPE_CONFERENCE,
|
||||||
|
// 学位论文
|
||||||
|
'dissertation' => self::TYPE_THESIS,
|
||||||
|
// 在线/预印本
|
||||||
|
'posted-content' => self::TYPE_WEB,
|
||||||
|
];
|
||||||
|
|
||||||
|
return isset($map[$t]) ? $map[$t] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 英文规则启发式:按「最独特 → 最普通」顺序判定
|
||||||
|
*
|
||||||
|
* @return array ['type' => string, 'confidence' => float]
|
||||||
|
*/
|
||||||
|
public function classifyByRule($text)
|
||||||
|
{
|
||||||
|
$hasDoi = (bool)preg_match('/\bdoi:\s*10\./i', $text) || (bool)preg_match('#doi\.org/#i', $text);
|
||||||
|
$hasUrl = (bool)preg_match('#https?://#i', $text);
|
||||||
|
// 期刊卷期页结构,如 2020;382(8):727-733 或 2020;10:100
|
||||||
|
$hasJournalVol = (bool)preg_match('/\b(19|20)\d{2}\s*[;:]\s*\d+\s*(\(\d+\))?\s*:\s*[A-Za-z]?\d+/', $text);
|
||||||
|
|
||||||
|
// 1) 学位论文
|
||||||
|
if (preg_match('/\[(ph\.?d\.?|master(\'s)?|doctoral|masters)?\s*(thesis|dissertation)\]/i', $text)
|
||||||
|
|| preg_match('/\b(ph\.?d\.?|master\'?s|doctoral|doctorate)\b[^.]{0,40}\b(thesis|dissertation)\b/i', $text)
|
||||||
|
|| preg_match('/\b(thesis|dissertation)\b/i', $text)) {
|
||||||
|
return ['type' => self::TYPE_THESIS, 'confidence' => 0.9];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 会议论文
|
||||||
|
if (preg_match('/\bproceedings\b/i', $text)
|
||||||
|
|| preg_match('/\bin:\s*proc\b/i', $text)
|
||||||
|
|| preg_match('/\b(conference|symposium|workshop|congress)\b/i', $text)
|
||||||
|
|| preg_match('/\bannual meeting\b/i', $text)) {
|
||||||
|
return ['type' => self::TYPE_CONFERENCE, 'confidence' => 0.85];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 网页 / 在线资源
|
||||||
|
if (preg_match('/\[(internet|online)\]/i', $text)
|
||||||
|
|| preg_match('/\baccessed\b/i', $text)
|
||||||
|
|| preg_match('/\bavailable\s+(from|at)\b/i', $text)
|
||||||
|
|| preg_match('/\bcited\s+(19|20)\d{2}/i', $text)
|
||||||
|
|| ($hasUrl && !$hasDoi && !$hasJournalVol)) {
|
||||||
|
return ['type' => self::TYPE_WEB, 'confidence' => 0.8];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) 图书
|
||||||
|
if (preg_match('/\b\d+(st|nd|rd|th)\s+ed(ition)?\.?/i', $text)
|
||||||
|
|| preg_match('/\bisbn\b/i', $text)
|
||||||
|
|| preg_match('/\b(press|publisher|publishing house)\b/i', $text)
|
||||||
|
|| preg_match('/[A-Z][A-Za-z .]+:\s*[A-Z][A-Za-z .&]+;\s*(19|20)\d{2}/', $text)) {
|
||||||
|
// 期刊卷期结构更强时不判为书
|
||||||
|
if (!$hasJournalVol) {
|
||||||
|
return ['type' => self::TYPE_BOOK, 'confidence' => 0.8];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) 期刊
|
||||||
|
if ($hasJournalVol || $hasDoi
|
||||||
|
|| preg_match('/\bvol\.?\s*\d+/i', $text)
|
||||||
|
|| preg_match('/\bpp?\.\s*\d+/i', $text)) {
|
||||||
|
return ['type' => self::TYPE_JOURNAL, 'confidence' => $hasJournalVol ? 0.85 : 0.6];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['type' => self::TYPE_OTHER, 'confidence' => 0.0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 大模型兜底:只返回枚举值之一,失败返回空串
|
||||||
|
*/
|
||||||
|
public function classifyByLlm($text)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$llm = new LLMService();
|
||||||
|
$system = 'You are a bibliography classifier. Classify the reference into exactly one type. '
|
||||||
|
. 'Allowed types: journal, book, conference, thesis, web, other. '
|
||||||
|
. 'journal=journal article; book=book or book chapter; conference=conference/proceedings paper; '
|
||||||
|
. 'thesis=dissertation/thesis; web=website or online resource; other=none of the above. '
|
||||||
|
. 'Respond with ONLY a JSON object: {"type":"<one of the allowed types>"}. No explanation.';
|
||||||
|
$user = "Reference:\n" . mb_substr($text, 0, 2000);
|
||||||
|
|
||||||
|
$content = $llm->requestChat([
|
||||||
|
['role' => 'system', 'content' => $system],
|
||||||
|
['role' => 'user', 'content' => $user],
|
||||||
|
], 0);
|
||||||
|
|
||||||
|
if ($content === null || $content === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $llm->parseJsonResponse($content);
|
||||||
|
$type = '';
|
||||||
|
if (is_array($parsed) && isset($parsed['type'])) {
|
||||||
|
$type = strtolower(trim((string)$parsed['type']));
|
||||||
|
} else {
|
||||||
|
// 兜底:直接从文本里找枚举词
|
||||||
|
if (preg_match('/\b(journal|book|conference|thesis|web|other)\b/i', $content, $m)) {
|
||||||
|
$type = strtolower($m[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowed = [
|
||||||
|
self::TYPE_JOURNAL, self::TYPE_BOOK, self::TYPE_CONFERENCE,
|
||||||
|
self::TYPE_THESIS, self::TYPE_WEB, self::TYPE_OTHER,
|
||||||
|
];
|
||||||
|
return in_array($type, $allowed, true) ? $type : '';
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user