作者堆叠 专刊

This commit is contained in:
wyn
2026-07-29 17:05:42 +08:00
parent f583f67e24
commit 1ee795b6b9
10 changed files with 2833 additions and 141 deletions

View File

@@ -10,6 +10,7 @@ use PhpOffice\PhpWord\IOFactory;
use app\common\OpenAi;
use app\common\CrossrefService;
use app\common\PubmedService;
use app\common\ArticleParserService;
/**
* @title 文章接口
@@ -1181,6 +1182,74 @@ class Article extends Base
return jsonSuccess($re);
}
/**
* @title 判断作者是否已按审稿意见修改(逐条核对最终版)
* @description 拆分审稿意见条目,结合修回稿与回复信,用 LLM 逐条判定 addressed/partial/not可缓存
* @param name:article_id type:int require:1 desc:文章id
* @param name:force type:int require:0 desc:1强制重跑忽略缓存
* @param name:mode type:string require:0 desc:full=逐条核对(默认) quick=仅时间门
*
* @url /api/Article/checkAuthorRevisedByReview
* @method POST
*/
public function checkAuthorRevisedByReview()
{
$data = $this->request->post();
$rule = new Validate([
'article_id' => 'require|number'
]);
if (!$rule->check($data)) {
return jsonError($rule->getError());
}
$articleId = intval($data['article_id']);
$mode = strtolower(trim((string)($data['mode'] ?? 'full')));
$force = !empty($data['force']) ? 1 : 0;
try {
$service = new \app\common\service\RevisionCommentMatchService();
if ($mode === 'quick') {
return jsonSuccess($service->quickCheck($articleId));
}
$result = $service->check($articleId, [
'force' => $force,
'use_cache' => $force ? 0 : 1,
]);
$result['mode'] = 'full';
return jsonSuccess($result);
} catch (\Exception $e) {
return jsonError($e->getMessage());
}
}
/**
* @title 获取最近一次逐条修回核对结果
* @param name:article_id type:int require:1 desc:文章id
* @url /api/Article/getRevisionCommentCheckResult
* @method POST
*/
public function getRevisionCommentCheckResult()
{
$data = $this->request->post();
$rule = new Validate([
'article_id' => 'require|number'
]);
if (!$rule->check($data)) {
return jsonError($rule->getError());
}
try {
$result = (new \app\common\service\RevisionCommentMatchService())
->getLatestResult(intval($data['article_id']));
if (empty($result)) {
return jsonError('暂无检测结果,请先调用 checkAuthorRevisedByReview');
}
return jsonSuccess($result);
} catch (\Exception $e) {
return jsonError($e->getMessage());
}
}
/**获取用户所投的文章
* @return \think\response\Json|void
@@ -3221,11 +3290,34 @@ class Article extends Base
}
/**
* 获取文章作者信息
* 支持 article_id查库或 file_url解析稿件不读写库
* @return void
*/
public function getAuthors()
{
$data = $this->request->post();
// 从稿件解析作者(对齐 Contribute 读取逻辑,不入库)
if (!empty($data['file_url'])) {
$sFileUrl = rtrim(ROOT_PATH, '/') . '/public/' . ltrim(ltrim($data['file_url'], '/'), 'public');
if (!file_exists($sFileUrl)) {
return jsonError('The uploaded file does not exist');
}
if (!is_readable($sFileUrl)) {
return jsonError('The uploaded file is unreadable');
}
$aDealData = json_decode(ArticleParserService::uploadAndParse($sFileUrl), true);
$iStatus = empty($aDealData['status']) ? 0 : $aDealData['status'];
if ($iStatus != 1) {
return jsonError(empty($aDealData['msg']) ? 'Content parsing failed' : $aDealData['msg']);
}
$aParseData = empty($aDealData['data']) ? [] : $aDealData['data'];
$re['authors'] = $this->buildAuthorsFromParseData($aParseData);
return jsonSuccess($re);
}
$rule = new Validate([
"article_id" => "require"
]);
@@ -3946,6 +4038,12 @@ class Article extends Base
if (isset($data['is_agree'])) {
$update_l['is_agree'] = $data['is_agree'];
}
if (isset($data['special_num'])) {
$update_l['special_num'] = intval($data['special_num']);
}
if (isset($data['special_title'])) {
$update_l['special_title'] = trim((string)$data['special_title']);
}
if(!empty($sArticleSn)){
$update_l['accept_sn'] = $sArticleSn;
@@ -5737,7 +5835,9 @@ class Article extends Base
// if(empty($iJournalId)){
// return json_encode(['status' => 2,'msg' => 'Please select a journal']);
// }
$aArticleInsert = ['journal_id' => $iJournalId,'title' => $sTitle,'state' => -1,'user_id' => $iUserId];
$aArticleInsert = ['journal_id' => $iJournalId,'title' => $sTitle,'abstrart' => '','use_ai_explain' => '','state' => -1,'user_id' => $iUserId];
if(isset($aParam['special_num']))$aArticleInsert['special_num'] = $aParam['special_num'];
if(isset($aParam['special_title']))$aArticleInsert['special_title'] = $aParam['special_title'];
$aArticleInsert['is_use_ai'] = 3;
$aArticleInsert['is_figure_copyright'] = 3;
// $aArticleInsert['is_transfer'] = 3;
@@ -5785,7 +5885,7 @@ class Article extends Base
if($becomeRev == false){
$aParam['is_become_reviewer'] = 2;
}
$aField = ['is_use_ai','use_ai_explain','is_figure_copyright','is_become_reviewer','approval','approval_file','approval_content','code','is_become_reviewer','is_agree','title','abstrart','keywords','topics','fund','type','journal_id'];//,'title','abstrart','keywords','topics','fund','type','is_transfer',
$aField = ['is_use_ai','use_ai_explain','is_figure_copyright','is_become_reviewer','approval','approval_file','approval_content','code','is_become_reviewer','is_agree','title','abstrart','keywords','topics','fund','type','journal_id','special_num','special_title'];//,'title','abstrart','keywords','topics','fund','type','is_transfer',
$sMsg = '';
$iIsUpdate = 1;
foreach ($aField as $key => $value) {
@@ -6412,9 +6512,65 @@ class Article extends Base
// $update_result = Db::name('article')->where($aWhere)->limit(1)->update($aArticleUpdate);
// }
//操作日志
$aLog = ['article_id' => $iArticleId,'user_id' => $iUserId,'type' => 7,'create_time' => time(),'content' => $sUserAccount . ':Operating the article copyright statement','is_view' => 1];
$aLog = ['article_id' => $iArticleId,'user_id' => $iUserId,'type' => 7,'create_time' => time(),'update_time' => time(),'content' => $sUserAccount . ':Operating the article copyright statement','is_view' => 1];
Db::name('user_act_log')->insert($aLog);
Db::commit();
return json_encode(['status' => 1,'msg' => 'success']);
}
/**
* 修改未发表文章对应专刊(仅更新 tougao 本地 t_article
* special_num = journal_special_idspecial_title 由前端一并传入
*/
public function changeArticleSpecialForSubmit()
{
$data = $this->request->post();
$rule = new Validate([
'article_id' => 'require',
/*'journal_special_id' => 'require',
'special_title' => 'require',*/
]);
if (!$rule->check($data)) {
return jsonError($rule->getError());
}
$article_info = $this->article_obj->where('article_id', $data['article_id'])->find();
if (!$article_info) {
return jsonError('article not found');
}
$journalSpecialRaw = isset($data['journal_special_id'])&&$data['journal_special_id'] ? trim((string)$data['journal_special_id']) : '';
// journal_special_id 为空:清空专刊信息
if ($journalSpecialRaw === '') {
$update = [
'special_num' => 0,
'special_title' => '',
];
$this->article_obj->where('article_id', $data['article_id'])->update($update);
return jsonSuccess([
'article_id' => intval($data['article_id']),
'special_num' => 0,
'special_title' => '',
]);
}
// journal_special_id 有值special_title 必填
$specialTitle = isset($data['special_title']) ? trim((string)$data['special_title']) : '';
if ($specialTitle === '') {
return jsonError('special_title is required');
}
$specialId = intval($journalSpecialRaw);
$update = [
'special_num' => $specialId,
'special_title' => $specialTitle,
];
$this->article_obj->where('article_id', $data['article_id'])->update($update);
return jsonSuccess([
'article_id' => intval($data['article_id']),
'special_num' => $specialId,
'special_title' => $update['special_title'],
]);
}
}

View File

@@ -25,6 +25,21 @@ class References extends Base
public function __construct(\think\Request $request = null) {
parent::__construct($request);
}
/**
* 参考文献堆叠统计可视化页
* 访问:/api/References/index?p_article_id=xxx
*/
public function index()
{
$pArticleId = intval($this->request->param('p_article_id', 0));
$this->assign([
'api_stats_js' => json_encode((string) url('api/References/referenceStackingStats'), JSON_UNESCAPED_SLASHES),
'init_p_article_id' => $pArticleId,
]);
return $this->fetch('references/index');
}
//OPENAI token
private $sApiKey = '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';
//OPENAI URL
@@ -1847,6 +1862,8 @@ class References extends Base
/**
* 参考文献引用堆叠统计(同作者>15%、同刊>20%、自引>10%,实时计算)
* 仅读本地库,不实时请求 Crossref/OpenAlex。
* 额外返回 author_data_issues未获取到作者 / 作者信息不全的参考文献列表
*
* POST/GET: p_article_id必填
*/
@@ -1871,6 +1888,66 @@ class References extends Base
}
}
/**
* 参考文献堆叠摘要(展示用)
* 返回:同作者名称+比例、同刊名称+比例、自引比例(仅超阈值的同作者/同刊)
*
* POST/GET: p_article_id必填
*/
public function referenceStackingSummary()
{
$aParam = $this->request->post();
if (empty($aParam)) {
$aParam = $this->request->param();
}
$iPArticleId = empty($aParam['p_article_id']) ? 0 : intval($aParam['p_article_id']);
if ($iPArticleId <= 0) {
return json_encode(array('status' => 2, 'msg' => 'Please select an article'));
}
try {
$svc = new ReferenceStackingStatsService();
$result = $svc->getStackingSummaryByPArticleId($iPArticleId);
return jsonSuccess($result);
} catch (\Exception $e) {
return jsonError($e->getMessage());
}
}
/**
* 获取单条参考文献作者信息:先 Crossref/OpenAlex或兜底 refer.author同步入库再返回明细
*
* POST/GET: p_article_id必填, p_refer_id必填
*/
public function referenceReferAuthors()
{
$aParam = $this->request->post();
if (empty($aParam)) {
$aParam = $this->request->param();
}
$iPArticleId = empty($aParam['p_article_id']) ? 0 : intval($aParam['p_article_id']);
$iPReferId = empty($aParam['p_refer_id']) ? 0 : intval($aParam['p_refer_id']);
if ($iPArticleId <= 0) {
return json_encode(array('status' => 2, 'msg' => 'Please select an article'));
}
if ($iPReferId <= 0) {
return jsonError('p_refer_id is required');
}
try {
$svc = new ReferenceReferAuthorService();
$result = $svc->fetchAuthorsByPReferId($iPArticleId, $iPReferId);
if (empty($result['refer'])) {
return jsonError('Reference not found');
}
return jsonSuccess($result);
} catch (\Exception $e) {
return jsonError($e->getMessage());
}
}
/**
* 测试:同步参考文献作者明细到 t_production_article_refer_author
* POST/GET: p_article_id必填, p_refer_id可选仅同步单条, include_authors可选默认 01 返回作者明细), sleep_ms可选默认 120

File diff suppressed because it is too large Load Diff

View File

@@ -2081,7 +2081,149 @@ class User extends Base
$re['user'] = $user_info;
return jsonSuccess($re);
}
public function createUserForEditor(){
$data = $this->request->post();
$rule = new Validate([
"email"=>"require",
"address"=>"require",
"firstname"=>"require",
"lastname"=>"require",
"intro"=>"require"
]);
if(!$rule->check($data)){
return jsonError($rule->getError());
}
$account = isset($data['account'])?$data['account']:$data['email'];
$email = $data['email'];
$res_once = $this->user_obj->where("account='$account' or email = '$email'")->find();
if ($res_once != null) {
return json('existence');
}
Db::startTrans();
$inser_data['account'] = trim($account);
$inser_data['password'] = md5(isset($data['password'])?$data['password']:"123456qwe");
$inser_data['email'] = $email;
if(isset($data['phone']))$inser_data['phone'] = $data['phone'];
if(isset($data['orcid']))$inser_data['orcid'] = $data['orcid'];
$inser_data['realname'] = $data['firstname']." ".$data['lastname'];
$inser_data['icon'] = $data['icon'];
$inser_data['ctime'] = time();
$inser_data['openid'] = "";
$id = $this->user_obj->insertGetId($inser_data);
//存入个人额外信息
$insert_reviewer['reviewer_id'] = $id;
$insert_reviewer['test_from'] = "kzeditor";
$insert_reviewer['address'] = $data['address'];
$insert_reviewer['introduction'] = $data['intro'];
$insert_reviewer['firstname'] = $data['firstname'];
$insert_reviewer['lastname'] = $data['lastname'];
if(isset($data['website']))$insert_reviewer['website'] = $data['website'];
if(isset($data['interests']))$insert_reviewer['interests'] = $data['interests'];
$r_res = $this->user_reviewer_info_obj->insert($insert_reviewer);
if($id&&$r_res){
Db::commit();
return jsonSuccess($id);
}else{
Db::rollback();
return jsonError("system error");
}
}
/**
* 修改客座编辑本地资料
* t_user: orcid、phone、realname(firstname lastname)、icon
* t_user_reviewer_info: address、interests、firstname、lastname、introduction(intro)
*/
public function updateUserForEditor()
{
$data = $this->request->post();
$rule = new Validate([
'user_id' => 'require|number',
'firstname' => 'require',
'lastname' => 'require',
'intro' => 'require',
]);
if (!$rule->check($data)) {
return jsonError($rule->getError());
}
$userId = intval($data['user_id']);
$user = $this->user_obj->where('user_id', $userId)->where('state', 0)->find();
if (!$user) {
return jsonError('user not found');
}
Db::startTrans();
try {
$firstName = trim((string)$data['firstname']);
$lastName = trim((string)$data['lastname']);
$userUpdate = [
'realname' => trim($firstName . ' ' . $lastName),
];
if (isset($data['orcid'])) {
$userUpdate['orcid'] = trim((string)$data['orcid']);
}
if (isset($data['phone'])) {
$userUpdate['phone'] = trim((string)$data['phone']);
}
if (isset($data['icon'])) {
$userUpdate['icon'] = trim((string)$data['icon']);
}
$upUser = $this->user_obj->where('user_id', $userId)->update($userUpdate);
if ($upUser === false) {
throw new \Exception('update t_user failed');
}
$reviewerInfo = $this->user_reviewer_info_obj
->where('reviewer_id', $userId)
->where('state', 0)
->find();
$reviewerUpdate = [
'firstname' => $firstName,
'lastname' => $lastName,
];
if (isset($data['address'])) {
$reviewerUpdate['address'] = trim((string)$data['address']);
}
if (isset($data['website'])) {
$reviewerUpdate['website'] = trim((string)$data['website']);
}
if (isset($data['interests'])) {
$reviewerUpdate['interests'] = trim((string)$data['interests']);
}
$reviewerUpdate['introduction'] = trim((string)$data['intro']);
if ($reviewerInfo) {
$upReviewer = $this->user_reviewer_info_obj
->where('reviewer_id', $userId)
->where('state', 0)
->update($reviewerUpdate);
if ($upReviewer === false) {
throw new \Exception('update t_user_reviewer_info failed');
}
} else {
$reviewerUpdate['reviewer_id'] = $userId;
$reviewerUpdate['test_from'] = 'kzeditor';
$insReviewer = $this->user_reviewer_info_obj->insert($reviewerUpdate);
if (!$insReviewer) {
throw new \Exception('insert t_user_reviewer_info failed');
}
}
Db::commit();
return jsonSuccess([
'user_id' => $userId,
'updated' => 1,
]);
} catch (\Exception $e) {
Db::rollback();
return jsonError($e->getMessage());
}
}
/**
* 注册功能
*/
@@ -3370,4 +3512,17 @@ class User extends Base
}
return implode("\n", $lines);
}
public function up_editorIcon_file()
{
$file = request()->file('icon');
if ($file) {
$info = $file->move(ROOT_PATH . 'public' . DS . 'usericon');
if ($info) {
return json(['code' => 0, 'upurl' => str_replace("\\", "/", $info->getSaveName())]);
} else {
return json(['code' => 1, 'msg' => $file->getError()]);
}
}
}
}