Files
tougao/application/api/controller/Author.php
2026-06-05 11:11:16 +08:00

395 lines
13 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\api\controller;
use app\common\service\AuthorBackgroundService;
use think\Controller;
/**
* 作者背调 API前后端分离返回 JSON
*
* 主接口background_report / due_diligenceORCID 必填)
* Scopus 相关接口委托 AuthorInfo 实现
*/
class Author extends Controller
{
/** @var AuthorBackgroundService */
private $bgService;
/** @var AuthorInfo */
private $authorInfo;
public function __construct(\think\Request $request = null)
{
parent::__construct($request);
$this->bgService = new AuthorBackgroundService();
$this->authorInfo = new AuthorInfo();
}
/**
* 作者背调 HTML 页面入口
*
* 1. 传了 ORCID → 直接生成报告
* 2. 未传 ORCID + 姓氏(机构选填)→ 仅按姓名搜 ORCID1 条直接报告,多条显示选择列表
*/
public function index()
{
@set_time_limit(120);
$formAction = $this->resolveFormAction();
$params = $this->resolveBackgroundParams();
$orcidNorm = $this->bgService->normalizeOrcid($params['orcid']);
if ($orcidNorm === ''
&& $params['last_name'] === ''
&& $params['first_name'] === ''
&& $params['institution'] === ''
) {
$this->assign('form_action', $formAction);
return $this->fetch('author/index');
}
// 1. 有 ORCID → 直接报告页
if ($orcidNorm !== '') {
return $this->renderReportPage($params, $formAction);
}
// 2. 无 ORCID → 姓氏必填,机构选填
if ($params['last_name'] === '') {
$this->assign([
'form_action' => $formAction,
'error_msg' => '未填 ORCID 时,请填写姓氏',
'last_name' => $params['last_name'],
'first_name' => $params['first_name'],
'institution' => $params['institution'],
]);
return $this->fetch('author/index');
}
// 3. 仅按姓名搜 ORCID机构只做排序校验
$search = $this->bgService->searchOrcidCandidates(
$params['last_name'],
$params['first_name'],
$params['institution']
);
$candidates = $search['candidates'] ?? [];
if (empty($candidates)) {
return $this->renderOrcidRequiredPage($params, $formAction, '已在 OpenAlex、ORCID 官网、Scopus 按姓名检索,未找到带 ORCID 的作者');
}
if (count($candidates) > 1) {
$this->assignCandidateListView($candidates, $params, $formAction);
return $this->fetch('author/select_orcid');
}
return $this->redirect($this->buildReportEntryUrl($formAction, $params, $candidates[0]['orcid']));
}
/**
* 医学期刊作者背景调查报告ORCID 必填)
*
* POST/GET 参数:
* orcid / orcid_id ORCID必填
* lastName / last_name 姓(选填,用于 PubMed 辅助检索与报告展示)
* firstName / first_name 名(选填)
* institution / affiliation 机构(选填)
*/
public function background_report()
{
@set_time_limit(120);
$params = $this->resolveBackgroundParams();
$result = $this->bgService->buildReport(
$params['orcid'],
$params['last_name'],
$params['first_name'],
$params['institution']
);
if (empty($result['ok'])) {
$code = !empty($result['need_select']) ? 2 : 0;
return json([
'code' => $code,
'msg' => $result['msg'] ?? '查询失败',
'data' => $result['data'] ?? null,
]);
}
return json([
'code' => 1,
'msg' => 'success',
'data' => $result['data'],
]);
}
/** camelCase 别名 */
public function backgroundReport()
{
return $this->background_report();
}
/** 与 background_report 相同(路由兼容) */
public function due_diligence()
{
return $this->background_report();
}
public function dueDiligence()
{
return $this->due_diligence();
}
/**
* OpenAlex + Crossref 诚信扫描(不依赖 ORCID 必填)
*/
public function background_check()
{
return $this->authorInfo->background_check();
}
public function backgroundCheck()
{
return $this->background_check();
}
public function get_hindex()
{
return $this->authorInfo->get_hindex();
}
public function getHindex()
{
return $this->get_hindex();
}
public function get_scopus_id()
{
return $this->authorInfo->get_scopus_id();
}
public function getScopusId()
{
return $this->get_scopus_id();
}
public function check_scopus_cookie()
{
return $this->authorInfo->check_scopus_cookie();
}
public function checkScopusCookie()
{
return $this->check_scopus_cookie();
}
public function save_scopus_cookie()
{
return $this->authorInfo->save_scopus_cookie();
}
public function saveScopusCookie()
{
return $this->save_scopus_cookie();
}
public function login_scopus()
{
return $this->authorInfo->login_scopus();
}
public function loginScopus()
{
return $this->login_scopus();
}
public function check_elsevier_api()
{
if (method_exists($this->authorInfo, 'check_elsevier_api')) {
return $this->authorInfo->check_elsevier_api();
}
return json(['code' => 0, 'msg' => 'check_elsevier_api not implemented']);
}
public function checkElsevierApi()
{
return $this->check_elsevier_api();
}
/**
* 解析背调查询参数(兼容多种命名)
*/
private function resolveBackgroundParams()
{
$pick = function (...$keys) {
foreach ($keys as $k) {
$v = trim((string) input('param.' . $k, ''));
if ($v === '') {
$v = trim((string) input('post.' . $k, ''));
}
if ($v === '') {
$v = trim((string) input('get.' . $k, ''));
}
if ($v !== '') {
return $v;
}
}
return '';
};
return [
'orcid' => $pick('orcid', 'orcid_id'),
'last_name' => $pick('lastName', 'last_name', 'lastname', 'surname'),
'first_name' => $pick('firstName', 'first_name', 'firstname', 'given_name'),
'institution' => $pick('institution', 'affiliation', 'affil', 'org'),
];
}
private function resolveFormAction()
{
return rtrim($this->request->root(), '/') . '/api/author/index';
}
private function renderReportPage(array $params, $formAction)
{
$result = $this->bgService->buildReport(
$params['orcid'],
$params['last_name'],
$params['first_name'],
$params['institution']
);
if (empty($result['ok'])) {
$data = $result['data'] ?? [];
if (!empty($result['need_select'])) {
$this->assignCandidateListView($data['candidates'] ?? [], $params, $formAction);
return $this->fetch('author/select_orcid');
}
if (!empty($data['orcid_required'])) {
return $this->renderOrcidRequiredPage($params, $formAction, $data['hint'] ?? '');
}
$this->assign([
'form_action' => $formAction,
'error_msg' => $result['msg'] ?? '查询失败',
]);
return $this->fetch('author/index');
}
$this->assignReportView($result['data'], $formAction);
return $this->fetch('author/report');
}
private function renderOrcidRequiredPage(array $params, $formAction, $hint = '')
{
$this->assign([
'form_action' => $formAction,
'submitted_name' => trim($params['first_name'] . ' ' . $params['last_name']),
'submitted_institution' => $params['institution'],
'last_name' => $params['last_name'],
'first_name' => $params['first_name'],
'institution' => $params['institution'],
'hint' => $hint,
]);
return $this->fetch('author/orcid_required');
}
private function buildReportEntryUrl($formAction, array $params, $orcid)
{
return $formAction . '?' . http_build_query(
array_filter([
'orcid' => $orcid,
'lastName' => $params['last_name'] ?? '',
'firstName' => $params['first_name'] ?? '',
'institution' => $params['institution'] ?? '',
], function ($v) {
return trim((string) $v) !== '';
}),
'',
'&',
PHP_QUERY_RFC3986
);
}
private function assignCandidateListView(array $candidates, array $params, $formAction)
{
foreach ($candidates as $idx => $item) {
$candidates[$idx]['report_url'] = $this->buildReportEntryUrl(
$formAction,
$params,
$item['orcid'] ?? ''
);
$candidates[$idx]['matched_class'] = !empty($item['institution_matched']) ? 'match' : '';
$name = trim((string) ($item['display_name'] ?? ''));
$candidates[$idx]['avatar_letter'] = $name !== ''
? mb_strtoupper(mb_substr($name, 0, 1))
: '?';
}
$this->assign([
'form_action' => $formAction,
'candidates' => $candidates,
'candidate_count' => count($candidates),
'submitted_name' => trim(($params['first_name'] ?? '') . ' ' . ($params['last_name'] ?? '')),
'submitted_institution' => $params['institution'] ?? '',
'last_name' => $params['last_name'] ?? '',
'first_name' => $params['first_name'] ?? '',
'institution' => $params['institution'] ?? '',
]);
}
private function assignReportView(array $report, $formAction)
{
$dupPaperCount = 0;
$duplicates = $report['duplicates'] ?? [];
foreach ($duplicates as $idx => $dg) {
$duplicates[$idx]['paper_count'] = count($dg['papers'] ?? []);
$dupPaperCount += $duplicates[$idx]['paper_count'];
foreach ($duplicates[$idx]['papers'] as $pi => $dp) {
$src = strtolower((string) ($dp['source'] ?? 'orcid'));
$duplicates[$idx]['papers'][$pi]['source_class'] = in_array($src, ['orcid', 'pubmed'], true) ? $src : 'orcid';
}
}
$report['duplicates'] = $duplicates;
$rw = $report['retraction_watch'] ?? [];
$items = $rw['items'] ?? [];
foreach ($items as $idx => $it) {
$title = !empty($it['author_title']) ? $it['author_title'] : ($it['title'] ?? '');
$items[$idx]['display_title'] = mb_substr($title, 0, 120);
$items[$idx]['reason_short'] = mb_substr((string) ($it['reason'] ?? ''), 0, 200);
$linkUrl = trim((string) ($it['url'] ?? ''));
if ($linkUrl === '') {
$linkUrl = 'https://retractionwatch.com/?s=' . rawurlencode((string) ($it['title'] ?? ''));
}
$items[$idx]['link_url'] = $linkUrl;
}
$report['retraction_watch']['items'] = $items;
$riskLevel = (string) ($report['conclusion']['risk_level'] ?? '');
$riskClass = 'risk-default';
if (strpos($riskLevel, '高风险') !== false) {
$riskClass = 'risk-high';
} elseif (strpos($riskLevel, '中风险') !== false) {
$riskClass = 'risk-mid';
} elseif (strpos($riskLevel, '低风险') !== false) {
$riskClass = 'risk-low';
}
$this->assign([
'form_action' => $formAction,
'report' => $report,
'risk_class' => $riskClass,
'orcid_affiliations_text' => implode('', $report['basic']['orcid_affiliations'] ?? []),
'openalex_institutions_text' => implode('', $report['basic']['openalex_institutions'] ?? []),
'topics_text' => implode('', $report['metrics']['topics'] ?? []),
'rw_match_total' => (int) ($rw['doi_match_count'] ?? 0)
+ (int) ($rw['name_match_count'] ?? 0)
+ (int) ($rw['name_loose_match_count'] ?? 0),
'dup_group_count' => count($duplicates),
'dup_paper_count' => $dupPaperCount,
'pubmed_list_count' => min(10, count($report['pubmed_papers'] ?? [])),
'orcid_section_num' => (($report['metrics']['pubmed_total'] ?? 0) > 0) ? '七' : '六',
]);
}
}