背调优化
发邮件记录添加姓名和邮箱模糊搜索
This commit is contained in:
@@ -4,6 +4,7 @@ namespace app\api\controller;
|
||||
|
||||
use app\common\service\AuthorBackgroundService;
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 作者背调:HTML 报告页 + JSON API
|
||||
@@ -15,6 +16,9 @@ class Author extends Controller
|
||||
/** @var AuthorBackgroundService */
|
||||
private $bgService;
|
||||
|
||||
/** @var string */
|
||||
private $articleLookupError = '';
|
||||
|
||||
public function __construct(\think\Request $request = null)
|
||||
{
|
||||
parent::__construct($request);
|
||||
@@ -25,7 +29,8 @@ class Author extends Controller
|
||||
* 作者背调 HTML 页面入口
|
||||
*
|
||||
* 1. 传了 ORCID → 直接生成报告
|
||||
* 2. 未传 ORCID + 姓氏(机构选填)→ 仅按姓名搜 ORCID;1 条直接报告,多条显示选择列表
|
||||
* 2. 传了 articleId(稿件作者 ID,即 art_aut_id)→ 从 t_article_author 补全 ORCID / 姓名 / 机构
|
||||
* 3. 未传 ORCID + 姓氏(机构选填)→ 仅按姓名搜 ORCID;1 条直接报告,多条显示选择列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
@@ -33,6 +38,18 @@ class Author extends Controller
|
||||
|
||||
$formAction = $this->resolveFormAction();
|
||||
$params = $this->resolveBackgroundParams();
|
||||
|
||||
if ($this->articleLookupError !== '') {
|
||||
$this->assign([
|
||||
'form_action' => $formAction,
|
||||
'error_msg' => $this->articleLookupError,
|
||||
'last_name' => $params['last_name'],
|
||||
'first_name' => $params['first_name'],
|
||||
'institution' => $params['institution'],
|
||||
]);
|
||||
return $this->fetch('author/index');
|
||||
}
|
||||
|
||||
$orcidNorm = $this->bgService->normalizeOrcid($params['orcid']);
|
||||
|
||||
if ($orcidNorm === ''
|
||||
@@ -129,6 +146,8 @@ class Author extends Controller
|
||||
*/
|
||||
private function resolveBackgroundParams()
|
||||
{
|
||||
$this->articleLookupError = '';
|
||||
|
||||
$pick = function (...$keys) {
|
||||
foreach ($keys as $k) {
|
||||
$v = trim((string) input('param.' . $k, ''));
|
||||
@@ -145,14 +164,142 @@ class Author extends Controller
|
||||
return '';
|
||||
};
|
||||
|
||||
$orcid = $pick('orcid', 'orcid_id');
|
||||
$lastName = $pick('lastName', 'last_name', 'lastname', 'surname');
|
||||
$firstName = $pick('firstName', 'first_name', 'firstname', 'given_name');
|
||||
$institution = $pick('institution', 'affiliation', 'affil', 'org');
|
||||
$realname = $pick('realname', 'real_name');
|
||||
$artAutId = $pick( 'art_aut_id', 'artAutId');
|
||||
|
||||
if ($artAutId !== '') {
|
||||
$fromAuthor = $this->loadAuthorByArtAutId($artAutId);
|
||||
if ($fromAuthor === null) {
|
||||
$this->articleLookupError = '未找到该作者信息';
|
||||
} else {
|
||||
if ($orcid === '') {
|
||||
$orcid = $fromAuthor['orcid'];
|
||||
}
|
||||
if ($lastName === '') {
|
||||
$lastName = $fromAuthor['last_name'];
|
||||
}
|
||||
if ($firstName === '') {
|
||||
$firstName = $fromAuthor['first_name'];
|
||||
}
|
||||
if ($institution === '') {
|
||||
$institution = $fromAuthor['institution'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($realname !== '' && ($lastName === '' || $firstName === '')) {
|
||||
$parsed = $this->parseRealname($realname);
|
||||
if ($lastName === '') {
|
||||
$lastName = $parsed['last_name'];
|
||||
}
|
||||
if ($firstName === '') {
|
||||
$firstName = $parsed['first_name'];
|
||||
}
|
||||
}
|
||||
|
||||
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'),
|
||||
'orcid' => $orcid,
|
||||
'last_name' => $lastName,
|
||||
'first_name' => $firstName,
|
||||
'institution' => $institution,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 art_aut_id 从 t_article_author 读取作者信息
|
||||
*/
|
||||
private function loadAuthorByArtAutId($artAutId)
|
||||
{
|
||||
$artAutId = (int) $artAutId;
|
||||
if ($artAutId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$row = Db::name('article_author')
|
||||
->field('orcid,firstname,lastname,company')
|
||||
->where(['art_aut_id' => $artAutId, 'state' => 0])
|
||||
->find();
|
||||
|
||||
if (empty($row)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'orcid' => trim((string) ($row['orcid'] ?? '')),
|
||||
'first_name' => trim((string) ($row['firstname'] ?? '')),
|
||||
'last_name' => trim((string) ($row['lastname'] ?? '')),
|
||||
'institution' => $this->extractInstitutionFromCompany($row['company'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 company 字段提取机构名(去掉序号前缀,支持 ; 和 , 分隔)
|
||||
*/
|
||||
private function extractInstitutionFromCompany($company)
|
||||
{
|
||||
$company = trim((string) $company);
|
||||
if ($company === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$company = str_replace(',', ',', $company);
|
||||
$parts = preg_split('/[;,]/u', $company);
|
||||
$institutions = [];
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$part = trim($part);
|
||||
if ($part === '') {
|
||||
continue;
|
||||
}
|
||||
$part = preg_replace('/^\d+\s*/', '', $part);
|
||||
$part = trim($part);
|
||||
if ($part !== '' && !in_array($part, $institutions, true)) {
|
||||
$institutions[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return implode(';', $institutions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将整段姓名拆成名+姓(如 Chuanying ZHANG → first=Chuanying, last=ZHANG)
|
||||
*/
|
||||
private function parseRealname($realname)
|
||||
{
|
||||
$realname = trim((string) $realname);
|
||||
if ($realname === '') {
|
||||
return ['first_name' => '', 'last_name' => ''];
|
||||
}
|
||||
|
||||
if (strpos($realname, ',') !== false) {
|
||||
$parts = array_map('trim', explode(',', $realname, 2));
|
||||
$family = $parts[0] ?? '';
|
||||
$given = $parts[1] ?? '';
|
||||
if ($family !== '' && $given !== '') {
|
||||
return ['first_name' => $given, 'last_name' => $family];
|
||||
}
|
||||
}
|
||||
|
||||
$tokens = preg_split('/\s+/u', $realname);
|
||||
$tokens = array_values(array_filter($tokens, function ($t) {
|
||||
return $t !== '';
|
||||
}));
|
||||
if (count($tokens) === 0) {
|
||||
return ['first_name' => '', 'last_name' => ''];
|
||||
}
|
||||
if (count($tokens) === 1) {
|
||||
return ['first_name' => '', 'last_name' => $tokens[0]];
|
||||
}
|
||||
|
||||
$lastName = array_pop($tokens);
|
||||
$firstName = implode(' ', $tokens);
|
||||
return ['first_name' => $firstName, 'last_name' => $lastName];
|
||||
}
|
||||
|
||||
private function resolveFormAction()
|
||||
{
|
||||
return rtrim($this->request->root(), '/') . '/api/author/index';
|
||||
|
||||
@@ -2130,6 +2130,7 @@ class EmailClient extends Base
|
||||
$state = $this->request->param('state', '-1');
|
||||
$page = max(1, intval($this->request->param('page', 1)));
|
||||
$perPage = max(1, min(intval($this->request->param('per_page', 50)), 200));
|
||||
$keyword = trim($this->request->param('keyword', ''));
|
||||
|
||||
if (!$taskId) {
|
||||
return jsonError('task_id is required');
|
||||
@@ -2139,7 +2140,9 @@ class EmailClient extends Base
|
||||
if ($state !== '-1' && $state !== '') {
|
||||
$where['l.state'] = intval($state);
|
||||
}
|
||||
|
||||
if ($keyword !== '') {
|
||||
$where['e.email|e.name'] = ['like',"%".trim($keyword)."%"];
|
||||
}
|
||||
$total = Db::name('promotion_email_log')->alias('l')->where($where)->count();
|
||||
$list = Db::name('promotion_email_log')->alias('l')
|
||||
->join('t_expert e', 'l.expert_id = e.expert_id', 'LEFT')
|
||||
|
||||
@@ -289,6 +289,57 @@ a.ext:hover { text-decoration: underline; }
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.report-rules {
|
||||
margin-top: 32px;
|
||||
padding: 18px 22px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
.report-rules-title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
.report-rules-sub {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
margin: 14px 0 8px;
|
||||
}
|
||||
.rules-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
.rules-table th,
|
||||
.rules-table td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
.rules-table th {
|
||||
background: #edf2f7;
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
width: 22%;
|
||||
}
|
||||
.rules-table td strong {
|
||||
color: var(--text);
|
||||
}
|
||||
.report-rules ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
.report-rules li {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.report-foot {
|
||||
margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--border);
|
||||
font-size: 12px; color: var(--text-muted); text-align: center; line-height: 1.7;
|
||||
|
||||
@@ -26,19 +26,19 @@
|
||||
<form method="get" action="{$form_action}">
|
||||
<div class="form-group">
|
||||
<label class="form-label">ORCID <span class="opt">— 填写后直接出报告</span></label>
|
||||
<input class="form-input" type="text" name="orcid" placeholder="0000-0002-6388-7847" value="{$orcid|default=''|htmlspecialchars}">
|
||||
<input class="form-input" type="text" name="orcid" value="{$orcid|default=''|htmlspecialchars}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">姓 Last Name <span class="opt">— 未填 ORCID 时必填</span></label>
|
||||
<input class="form-input" type="text" name="lastName" placeholder="PENG" value="{$last_name|default=''|htmlspecialchars}">
|
||||
<input class="form-input" type="text" name="lastName" value="{$last_name|default=''|htmlspecialchars}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">名 First Name <span class="opt">— 选填</span></label>
|
||||
<input class="form-input" type="text" name="firstName" placeholder="Sijing" value="{$first_name|default=''|htmlspecialchars}">
|
||||
<input class="form-input" type="text" name="firstName" value="{$first_name|default=''|htmlspecialchars}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">机构 Institution <span class="opt">— 选填,用于候选列表排序</span></label>
|
||||
<input class="form-input" type="text" name="institution" placeholder="University of Ibadan" value="{$institution|default=''|htmlspecialchars}">
|
||||
<input class="form-input" type="text" name="institution" value="{$institution|default=''|htmlspecialchars}">
|
||||
</div>
|
||||
<button class="btn-primary" type="submit">生成背调报告</button>
|
||||
</form>
|
||||
|
||||
@@ -239,6 +239,32 @@
|
||||
</div>
|
||||
{/notempty}
|
||||
|
||||
<div class="report-rules">
|
||||
<p class="report-rules-title">学术指标说明</p>
|
||||
<table class="rules-table">
|
||||
<tr><th>论文总数</th><td>取 <strong>OpenAlex、ORCID、PubMed、Scopus</strong> 四项中的<strong>最大值</strong>(非相加)。OpenAlex 无档案时,可由 ORCID / PubMed 等补全。</td></tr>
|
||||
<tr><th>总被引</th><td>仅来自 <strong>OpenAlex</strong>;未匹配到作者档案时为 0。</td></tr>
|
||||
<tr><th>H 指数</th><td>仅来自 <strong>OpenAlex</strong>;未匹配时为 0。</td></tr>
|
||||
<tr><th>i10 指数</th><td>仅来自 <strong>OpenAlex</strong>(至少 10 次被引的论文数);未匹配时为 0。</td></tr>
|
||||
<tr><th>PubMed</th><td>独立统计,与论文总数<strong>口径不同</strong>。检索顺序:ORCID → 姓名+机构 → 姓名,采用第一个有结果的检索式;列表最多显示最近 10 篇。</td></tr>
|
||||
<tr><th>研究方向</th><td>来自 OpenAlex 前 5 个主题;无 OpenAlex 档案时不显示。</td></tr>
|
||||
</table>
|
||||
<p class="report-rules-sub">风险评级</p>
|
||||
<ul>
|
||||
<li><strong>高风险</strong>:Retraction Watch 存在学术不端相关记录</li>
|
||||
<li><strong>中风险</strong>:存在撤稿 / 关注声明</li>
|
||||
<li><strong>待核实</strong>:论文总数为 0</li>
|
||||
<li><strong>低风险</strong>:H ≥ 10 或论文总数 ≥ 20,且无上述不良记录</li>
|
||||
<li><strong>一般</strong>:其余情况(青年学者常见产出区间)</li>
|
||||
</ul>
|
||||
<p class="report-rules-sub">其他说明</p>
|
||||
<ul>
|
||||
<li>OpenAlex 匹配:优先 ORCID 直连;有 ORCID 时不使用同名他人数据。</li>
|
||||
<li>诚信记录:有 DOI 的 ORCID 作品按 DOI 精确比对;无 DOI 时回退姓名+题目匹配(同名需人工核实)。</li>
|
||||
<li>论文总数有值、总被引/H 为 0:通常表示 OpenAlex 未收录该作者,不代表无学术产出。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p class="report-foot">
|
||||
数据来源:OpenAlex · ORCID · PubMed · Scopus · Retraction Watch<br>
|
||||
适用于青年编委 / 特约审稿人 / 作者资质初审
|
||||
|
||||
@@ -989,9 +989,14 @@ class AuthorBackgroundService
|
||||
}
|
||||
$papers = $this->pubmedFetchSummaries($ids);
|
||||
}
|
||||
$urlTerm = $usedTerm;
|
||||
if (preg_match('/^(.+)\[ORCID\]$/i', $usedTerm, $m)) {
|
||||
$urlTerm = $m[1];
|
||||
}
|
||||
|
||||
return [
|
||||
'total' => $total, 'papers' => $papers, 'query' => $usedTerm,
|
||||
'pubmed_url' => 'https://pubmed.ncbi.nlm.nih.gov/?term=' . urlencode($usedTerm),
|
||||
'pubmed_url' => 'https://pubmed.ncbi.nlm.nih.gov/?term=' . urlencode($urlTerm),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user