This commit is contained in:
wangjinlei
2026-04-16 13:30:31 +08:00
parent a2338340f6
commit ae221e6be6
8 changed files with 838 additions and 91 deletions

View File

@@ -3,7 +3,9 @@
namespace app\common;
use think\Db;
use think\Queue;
use GuzzleHttp\Client;
use think\Env;
class ExpertFinderService
{
@@ -74,64 +76,105 @@ class ExpertFinderService
$fieldEnrich = 0;
foreach ($experts as $expert) {
$email = strtolower(trim($expert['email']));
if (empty($email)) {
continue;
}
$exists = Db::name('expert')->where('email', $email)->find();
$exists = Db::name('expert')->where('email', $email)->find();
$expertId = null;
if ($exists) {
$existing++;
$fieldEnrich += $this->enrichExpertField($exists['expert_id'], $field);
continue;
}
$insert = [
'name' => mb_substr($expert['name'], 0, 255),
'email' => mb_substr($email, 0, 128),
'affiliation' => mb_substr($expert['affiliation'], 0, 128),
'source' => mb_substr($source, 0, 128),
'ctime' => time(),
'ltime' => 0,
'state' => 0,
];
try {
$expertId = Db::name('expert')->insertGetId($insert);
$this->enrichExpertField($expertId, $field);
if(isset($expert['papers'])&&is_array($expert['papers'])){
$this->savePaper($expertId, $expert['papers']);
$expertId = intval($exists['expert_id']);
} else {
try {
$expertId = Db::name('expert')->insertGetId([
'name' => mb_substr($expert['name'], 0, 255),
'email' => mb_substr($email, 0, 128),
'affiliation' => mb_substr($expert['affiliation'], 0, 128),
'source' => mb_substr($source, 0, 128),
'ctime' => time(),
'ltime' => 0,
'state' => 0,
]);
$inserted++;
} catch (\Exception $e) {
$existing++;
continue;
}
$inserted++;
} catch (\Exception $e) {
$existing++;
}
$papers = (isset($expert['papers']) && is_array($expert['papers'])) ? $expert['papers'] : [];
$fieldEnrich += $this->saveFieldWithPapers($expertId, $field, $source, $papers);
}
return ['inserted' => $inserted, 'existing' => $existing, 'field_enriched' => $fieldEnrich];
}
private function savePaper($expertId, $papers)
/**
* 保存领域与论文的关联。
* 有论文时每篇论文一行expert_id + field + source + paper_article_id 去重)。
* 无论文时只存一条领域行expert_id + field 去重)。
*/
private function saveFieldWithPapers($expertId, $field, $source, $papers)
{
foreach ($papers as $paper){
$check = Db::name('expert_paper')->where("expert_id",$expertId)->where('paper_article_id',$paper['article_id'])->find();
if($check){
continue;
}
$insert = [
'expert_id' => $expertId,
'paper_title' => isset($paper['title'])?mb_substr($paper['title'], 0, 255):"",
'paper_article_id' => $paper['article_id'] ?? 0,
'paper_journal' => isset($paper['journal'])?mb_substr($paper['journal'], 0, 128):"",
'ctime' => time(),
];
Db::name('expert_paper')->insert($insert);
}
}
$field = trim($field);
if (empty($field)) return 0;
$added = 0;
if (empty($papers)) {
$exists = Db::name('expert_field')
->where('expert_id', $expertId)
->where('field', $field)
->where('state', 0)
->find();
if (!$exists) {
Db::name('expert_field')->insert([
'expert_id' => $expertId,
'source' => mb_substr((string)$source, 0, 64),
'field' => mb_substr($field, 0, 128),
'paper_title' => '',
'paper_article_id' => '',
'paper_journal' => '',
'state' => 0,
]);
$added = 1;
}
} else {
foreach ($papers as $paper) {
$articleId = isset($paper['article_id']) ? (string)$paper['article_id'] : '';
if ($articleId === '' || $articleId === '0') {
continue;
}
$check = Db::name('expert_field')
->where('expert_id', $expertId)
->where('field', $field)
->where('source', $source)
->where('paper_article_id', $articleId)
->where('state', 0)
->find();
if ($check) {
continue;
}
Db::name('expert_field')->insert([
'expert_id' => $expertId,
'source' => mb_substr((string)$source, 0, 64),
'paper_title' => isset($paper['title']) ? mb_substr((string)$paper['title'], 0, 255) : '',
'paper_article_id' => mb_substr($articleId, 0, 64),
'paper_journal' => isset($paper['journal']) ? mb_substr((string)$paper['journal'], 0, 255) : '',
'field' => mb_substr($field, 0, 128),
'state' => 0,
]);
$added++;
}
}
return $added;
}
public function getFetchLog($field, $source)
{
@@ -536,25 +579,94 @@ class ExpertFinderService
];
}
// ==================== DB Helpers ====================
// ==================== Country Resolution ====================
private function enrichExpertField($expertId, $field)
/**
* 启动国家解析链:找到下一个缺国家的专家推入队列。
* 队列 Job 处理完一个后会再调此方法,自动找下一个,直到全部处理完。
* 控制器只需调一次即可。
*
* @param int $delay 延迟秒数防止打满模型默认1秒
* @return bool 是否成功推入了一条
*/
public function enqueueNextCountryFill($delay = 1)
{
$field = trim($field);
if (empty($field)) return 0;
$exists = Db::name('expert_field')
->where('expert_id', $expertId)
->where('field', $field)
->where('state', 0)
$row = Db::name('expert')
->where('affiliation', '<>', '')
->where(function ($q) {
$q->where('country_id', 0)
->whereOr('country_id', 'null')
->whereOr('country', '');
})
->where('state', '<>', 5)
->field('expert_id, affiliation')
->order('expert_id asc')
->find();
if ($exists) return 0;
Db::name('expert_field')->insert([
'expert_id' => $expertId,
'field' => mb_substr($field, 0, 128),
'state' => 0,
if (!$row) {
$this->log('[CountryFill] no more pending experts');
return false;
}
$data = [
'expert_id' => intval($row['expert_id']),
'affiliation' => trim((string)$row['affiliation']),
];
if ($delay > 0) {
Queue::later($delay, 'app\api\job\FillExpertCountry@fire', $data, 'FetchExperts');
} else {
Queue::push('app\api\job\FillExpertCountry@fire', $data, 'FetchExperts');
}
return true;
}
/**
* 对单个专家执行国家解析(同步),由队列 Job FillExpertCountry 调用,也可直接调用测试。
*/
public function fillExpertCountry($expertId, $affiliation)
{
$affiliation = trim((string)$affiliation);
if ($affiliation === '') return;
$resolver = new CountryResolverService([
'chat_url' => trim((string)Env::get('expert_country_chat_url', Env::get('citation_chat_url', 'http://chat.taimed.cn/v1/chat/completions'))),
'chat_model' => trim((string)Env::get('expert_country_chat_model', Env::get('citation_chat_model', 'gpt-4.1'))),
'api_key' => trim((string)Env::get('expert_country_chat_api_key', Env::get('citation_chat_api_key', ''))),
'timeout' => max(20, intval(Env::get('expert_country_chat_timeout', 60))),
]);
return 1;
$result = $resolver->resolve($affiliation);
if (empty($result)) return;
$countryId = 0;
$enName = '';
if (!empty($result['code'])) {
$row = Db::name('country')->where('code', strtoupper(trim((string)$result['code'])))->find();
if ($row) {
$countryId = intval($row['country_id']);
$enName = (string)$row['en_name'];
}
}
if ($countryId === 0 && !empty($result['en_name'])) {
$row = Db::name('country')
->whereRaw("LOWER(en_name) = ?", [strtolower(trim((string)$result['en_name']))])
->find();
if ($row) {
$countryId = intval($row['country_id']);
$enName = (string)$row['en_name'];
}
}
if ($countryId > 0 && $enName !== '') {
Db::name('expert')->where('expert_id', intval($expertId))->update([
'country_id' => $countryId,
'country' => $enName,
]);
}
}
// ==================== Text Helpers ====================