Merge branch 'master' of https://git.nuttyreading.com/zm/tougao
This commit is contained in:
386
application/api/controller/BackgroundCheck.php
Normal file
386
application/api/controller/BackgroundCheck.php
Normal file
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use think\Cache;
|
||||
use app\common\BackgroundCheckService;
|
||||
|
||||
/**
|
||||
* 背景调查控制器
|
||||
*
|
||||
* 功能:查询学者公开学术指标 + 撤稿/不端公开记录 + 按领域批量筛查
|
||||
*
|
||||
* 接口:
|
||||
* api/background_check/demo - 快速体验
|
||||
* api/background_check/searchAuthor - 按姓名搜索学者
|
||||
* api/background_check/checkProfile - 完整背景调查报告
|
||||
* api/background_check/checkRetractionByDoi - 单篇 DOI 撤稿详情(CrossRef + RW)
|
||||
* api/background_check/batchScreenByField - 按领域批量筛查专家
|
||||
*/
|
||||
class BackgroundCheck extends Base
|
||||
{
|
||||
/** @var BackgroundCheckService */
|
||||
private $service;
|
||||
|
||||
public function __construct(\think\Request $request = null)
|
||||
{
|
||||
parent::__construct($request);
|
||||
$this->service = new BackgroundCheckService();
|
||||
}
|
||||
|
||||
// ===================== 公开 API =====================
|
||||
|
||||
/**
|
||||
* Demo:快速体验
|
||||
*
|
||||
* @param string orcid 可选,默认 0000-0002-2582-7480
|
||||
*/
|
||||
public function demo()
|
||||
{
|
||||
$orcid = $this->service->cleanOrcid($this->request->param('orcid', '0000-0002-2582-7480'));
|
||||
|
||||
$report = $this->buildProfileReport([
|
||||
'orcid' => $orcid,
|
||||
'with_crossref_detail'=> 1,
|
||||
]);
|
||||
if (!$report['success']) {
|
||||
return jsonError($report['error']);
|
||||
}
|
||||
|
||||
$report['data']['_demo_note'] = 'Demo 接口,正式使用请调用 checkProfile 或 batchScreenByField';
|
||||
return jsonSuccess($report['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按姓名搜索学者
|
||||
*
|
||||
* @param string name 学者姓名(必填)
|
||||
* @param string affiliation 单位关键词(可选)
|
||||
* @param int limit 返回条数,默认5,最大20
|
||||
*/
|
||||
public function searchAuthor()
|
||||
{
|
||||
$name = trim($this->request->param('name', ''));
|
||||
if ($name === '') {
|
||||
return jsonError('name不能为空');
|
||||
}
|
||||
|
||||
$affiliation = trim($this->request->param('affiliation', ''));
|
||||
$limit = min(max(intval($this->request->param('limit', 5)), 1), 20);
|
||||
|
||||
$cacheKey = 'bg_search_' . md5($name . $affiliation . $limit);
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached) {
|
||||
return jsonSuccess($cached);
|
||||
}
|
||||
|
||||
$filter = 'display_name.search:' . $name;
|
||||
if ($affiliation !== '') {
|
||||
$filter .= ',last_known_institutions.display_name.search:' . $affiliation;
|
||||
}
|
||||
|
||||
$res = $this->service->openAlexGet('/authors', [
|
||||
'search' => $name,
|
||||
'filter' => $filter,
|
||||
'sort' => 'cited_by_count:desc',
|
||||
'per-page' => $limit,
|
||||
]);
|
||||
|
||||
if (!$res['success']) {
|
||||
return jsonError($res['error']);
|
||||
}
|
||||
|
||||
$list = [];
|
||||
foreach ($res['data']['results'] ?? [] as $author) {
|
||||
$list[] = $this->service->formatAuthorBrief($author);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'query' => ['name' => $name, 'affiliation' => $affiliation],
|
||||
'total' => $res['data']['meta']['count'] ?? count($list),
|
||||
'list' => $list,
|
||||
];
|
||||
|
||||
Cache::set($cacheKey, $result, 1800);
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整背景调查报告
|
||||
*
|
||||
* @param string openalex_id OpenAlex 作者ID
|
||||
* @param string orcid ORCID
|
||||
* @param string name 姓名
|
||||
* @param string affiliation 单位关键词
|
||||
* @param int user_id 本地用户ID(可选)
|
||||
* @param int with_crossref_detail 是否补充 CrossRef 撤稿详情,默认1
|
||||
*/
|
||||
public function checkProfile()
|
||||
{
|
||||
$params = [
|
||||
'openalex_id' => trim($this->request->param('openalex_id', '')),
|
||||
'orcid' => $this->service->cleanOrcid($this->request->param('orcid', '')),
|
||||
'name' => trim($this->request->param('name', '')),
|
||||
'affiliation' => trim($this->request->param('affiliation', '')),
|
||||
'user_id' => intval($this->request->param('user_id', 0)),
|
||||
'with_crossref_detail' => intval($this->request->param('with_crossref_detail', 1)),
|
||||
];
|
||||
|
||||
if ($params['openalex_id'] === '' && $params['orcid'] === '' && $params['name'] === '') {
|
||||
return jsonError('请至少提供 openalex_id、orcid 或 name 之一');
|
||||
}
|
||||
|
||||
$report = $this->buildProfileReport($params);
|
||||
if (!$report['success']) {
|
||||
return jsonError($report['error']);
|
||||
}
|
||||
|
||||
return jsonSuccess($report['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单篇 DOI 撤稿详情(CrossRef + Retraction Watch 来源标记)
|
||||
*
|
||||
* @param string doi 文章 DOI(必填)
|
||||
*/
|
||||
public function checkRetractionByDoi()
|
||||
{
|
||||
$doi = trim($this->request->param('doi', ''));
|
||||
if ($doi === '') {
|
||||
return jsonError('doi不能为空');
|
||||
}
|
||||
|
||||
$res = $this->service->fetchCrossRefWork($doi);
|
||||
if (!$res['success']) {
|
||||
return jsonError($res['error']);
|
||||
}
|
||||
|
||||
$detail = $this->service->parseCrossRefRetractionDetail($doi, $res['message']);
|
||||
$sources = $detail['retraction_detail']['sources'] ?? [];
|
||||
$fromRw = false;
|
||||
foreach ($sources as $src) {
|
||||
if (stripos($src, 'retraction-watch') !== false || stripos($src, 'retraction_watch') !== false) {
|
||||
$fromRw = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return jsonSuccess([
|
||||
'doi' => $detail['doi'],
|
||||
'title' => $detail['title'],
|
||||
'journal' => $detail['journal'],
|
||||
'publisher' => $detail['publisher'],
|
||||
'authors' => $detail['authors'],
|
||||
'is_retracted' => $detail['is_retracted'],
|
||||
'from_retraction_watch' => $fromRw || !empty($detail['retraction_detail']['record_ids']),
|
||||
'retraction_detail' => $detail['retraction_detail'],
|
||||
'url' => $detail['url'],
|
||||
'data_sources' => ['CrossRef', 'Retraction Watch'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按领域批量筛查专家(含撤稿风险标签)
|
||||
*
|
||||
* @param string keyword 领域关键词(必填),如 immunotherapy、cancer
|
||||
* @param int min_h_index 最低 h-index,默认5
|
||||
* @param int limit 每页数量,默认10,最大30
|
||||
* @param int page 页码,默认1
|
||||
* @param int check_retraction 是否检查撤稿,默认1
|
||||
*/
|
||||
public function batchScreenByField()
|
||||
{
|
||||
$keyword = trim($this->request->param('keyword', ''));
|
||||
if ($keyword === '') {
|
||||
return jsonError('keyword不能为空');
|
||||
}
|
||||
|
||||
$options = [
|
||||
'min_h_index' => intval($this->request->param('min_h_index', 5)),
|
||||
'limit' => min(max(intval($this->request->param('limit', 10)), 1), 30),
|
||||
'page' => max(intval($this->request->param('page', 1)), 1),
|
||||
];
|
||||
$checkRetraction = intval($this->request->param('check_retraction', 1));
|
||||
|
||||
$cacheKey = 'bg_batch_' . md5(json_encode([$keyword, $options, $checkRetraction]));
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached) {
|
||||
return jsonSuccess($cached);
|
||||
}
|
||||
|
||||
$searchRes = $this->service->searchAuthorsByField($keyword, $options);
|
||||
if (!$searchRes['success']) {
|
||||
return jsonError($searchRes['error']);
|
||||
}
|
||||
|
||||
$data = $searchRes['data'];
|
||||
$screened = [];
|
||||
$riskStats = ['low' => 0, 'medium' => 0, 'high' => 0];
|
||||
|
||||
foreach ($data['list'] as $author) {
|
||||
$item = [
|
||||
'profile' => $author,
|
||||
'metrics' => [
|
||||
'works_count' => $author['works_count'],
|
||||
'cited_by_count' => $author['cited_by_count'],
|
||||
'h_index' => $author['h_index'],
|
||||
'level_label' => $this->service->parseAuthorMetrics([
|
||||
'works_count' => $author['works_count'],
|
||||
'cited_by_count' => $author['cited_by_count'],
|
||||
'summary_stats' => ['h_index' => $author['h_index']],
|
||||
])['level_label'],
|
||||
],
|
||||
];
|
||||
|
||||
if ($checkRetraction) {
|
||||
$openAlexId = $author['openalex_id'];
|
||||
$oaRetractions = $this->service->fetchRetractedWorksOpenAlex($openAlexId);
|
||||
$rwRetractions = $this->service->fetchRetractionWatchByAuthor($author['name']);
|
||||
$merged = $this->service->mergeRetractionRecords($oaRetractions, $rwRetractions, false);
|
||||
|
||||
$metrics = ['works_count' => $author['works_count']];
|
||||
$risk = $this->service->assessRisk($metrics, $merged);
|
||||
|
||||
$item['retractions'] = [
|
||||
'count' => $merged['count'],
|
||||
'openalex_count'=> $merged['openalex_count'],
|
||||
'rw_count' => $merged['rw_count'],
|
||||
'rw_only_count' => $merged['rw_only_count'],
|
||||
'list' => array_slice($merged['list'], 0, 3),
|
||||
];
|
||||
$item['risk_assessment'] = $risk;
|
||||
$riskStats[$risk['level']]++;
|
||||
} else {
|
||||
$item['risk_assessment'] = [
|
||||
'level' => 'unknown',
|
||||
'level_label' => '未检测',
|
||||
];
|
||||
}
|
||||
|
||||
$screened[] = $item;
|
||||
usleep(300000);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'query' => [
|
||||
'keyword' => $keyword,
|
||||
'topic_id' => $data['topic_id'],
|
||||
'min_h_index' => $options['min_h_index'],
|
||||
'page' => $options['page'],
|
||||
'limit' => $options['limit'],
|
||||
'check_retraction' => $checkRetraction,
|
||||
],
|
||||
'total' => $data['total'],
|
||||
'risk_stats' => $checkRetraction ? $riskStats : null,
|
||||
'list' => $screened,
|
||||
'disclaimer' => '批量筛查基于公开数据,同名作者可能存在误匹配,高风险条目需人工复核。',
|
||||
];
|
||||
|
||||
Cache::set($cacheKey, $result, 900);
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
// ===================== 核心逻辑 =====================
|
||||
|
||||
/**
|
||||
* 构建完整背景调查报告
|
||||
*/
|
||||
private function buildProfileReport($params)
|
||||
{
|
||||
$withCrossRef = !empty($params['with_crossref_detail']);
|
||||
|
||||
$author = $this->service->resolveAuthor($params);
|
||||
if (!$author['success']) {
|
||||
return $author;
|
||||
}
|
||||
|
||||
$authorData = $author['data'];
|
||||
$openAlexId = $this->service->extractOpenAlexId($authorData['id']);
|
||||
$authorName = $authorData['display_name'] ?? '';
|
||||
|
||||
$metrics = $this->service->parseAuthorMetrics($authorData);
|
||||
$topics = $this->service->parseResearchTopics($authorData);
|
||||
$recentWorks = $this->service->fetchRecentWorks($openAlexId, 5);
|
||||
|
||||
// 扩展1: OpenAlex 撤稿
|
||||
$oaRetractions = $this->service->fetchRetractedWorksOpenAlex($openAlexId);
|
||||
|
||||
// 扩展1: Retraction Watch 二次核对(CrossRef 来源)
|
||||
$rwRetractions = $this->service->fetchRetractionWatchByAuthor($authorName);
|
||||
|
||||
// 扩展2: 合并并按需补充 CrossRef 撤稿详情
|
||||
$retractions = $this->service->mergeRetractionRecords(
|
||||
$oaRetractions,
|
||||
$rwRetractions,
|
||||
$withCrossRef
|
||||
);
|
||||
|
||||
$risk = $this->service->assessRisk($metrics, $retractions);
|
||||
|
||||
$localInfo = null;
|
||||
if (!empty($params['user_id'])) {
|
||||
$localInfo = $this->fetchLocalUserInfo($params['user_id']);
|
||||
}
|
||||
|
||||
$dataSources = ['OpenAlex', 'Retraction Watch (via CrossRef)'];
|
||||
if ($withCrossRef) {
|
||||
$dataSources[] = 'CrossRef';
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'profile' => $this->service->formatAuthorBrief($authorData),
|
||||
'metrics' => $metrics,
|
||||
'research_topics' => $topics,
|
||||
'recent_works' => $recentWorks,
|
||||
'retractions' => $retractions,
|
||||
'risk_assessment' => $risk,
|
||||
'local_info' => $localInfo,
|
||||
'data_sources' => $dataSources,
|
||||
'disclaimer' => '本报告仅基于公开学术数据,不能替代正式背调或机构调查;未公开的不端行为无法检测。',
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并本地用户信息
|
||||
*/
|
||||
private function fetchLocalUserInfo($userId)
|
||||
{
|
||||
$user = $this->user_obj->where('user_id', $userId)->find();
|
||||
if (!$user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$reviewer = $this->user_reviewer_info_obj
|
||||
->where('reviewer_id', $userId)
|
||||
->where('state', 0)
|
||||
->find();
|
||||
|
||||
$majors = $this->major_to_user_obj
|
||||
->where('user_id', $userId)
|
||||
->where('state', 0)
|
||||
->select();
|
||||
|
||||
$majorList = [];
|
||||
foreach ($majors as $m) {
|
||||
$majorList[] = [
|
||||
'major_id' => $m['major_id'],
|
||||
'path' => getMajorStr($m['major_id']),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'user_id' => $userId,
|
||||
'realname' => $user['realname'] ?? '',
|
||||
'email' => $user['email'] ?? '',
|
||||
'orcid' => $user['orcid'] ?? '',
|
||||
'company' => $reviewer['company'] ?? '',
|
||||
'title' => $reviewer['technical'] ?? '',
|
||||
'field' => $reviewer['field'] ?? '',
|
||||
'majors' => $majorList,
|
||||
];
|
||||
}
|
||||
}
|
||||
161
application/api/controller/ExpertFieldAi.php
Normal file
161
application/api/controller/ExpertFieldAi.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use think\Db;
|
||||
use app\common\ExpertFieldAiService;
|
||||
|
||||
/**
|
||||
* Expert 领域总结(方案 C - 阶段1:邮箱关联 user.field_ai)
|
||||
*
|
||||
* POST startLinkChain 启动链式队列,批量关联
|
||||
* POST linkOne 同步关联单个 expert_id
|
||||
* POST linkBatch 同步批量关联 expert_ids
|
||||
* POST syncByUser user 有 field_ai 后,同步到同邮箱 expert
|
||||
* GET preview 预览是否可关联
|
||||
* GET statistics 统计 field_ai 覆盖情况
|
||||
*/
|
||||
class ExpertFieldAi extends Base
|
||||
{
|
||||
/**
|
||||
* 启动链式关联队列
|
||||
* Worker: php think queue:work --queue ExpertFieldAi
|
||||
*/
|
||||
public function startLinkChain()
|
||||
{
|
||||
$force = intval($this->request->param('force', 0)) === 1;
|
||||
$delay = max(0, intval($this->request->param('delay', 1)));
|
||||
|
||||
$svc = new ExpertFieldAiService();
|
||||
$started = $svc->startLinkChain($force, $delay);
|
||||
|
||||
return jsonSuccess([
|
||||
'started' => $started,
|
||||
'queue' => ExpertFieldAiService::QUEUE_NAME,
|
||||
'force' => $force,
|
||||
'msg' => $started ? 'link chain enqueued' : 'no pending experts',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步关联单个 expert
|
||||
*/
|
||||
public function linkOne()
|
||||
{
|
||||
$expertId = intval($this->request->param('expert_id', 0));
|
||||
$force = intval($this->request->param('force', 0)) === 1;
|
||||
if ($expertId <= 0) {
|
||||
return jsonError('expert_id required');
|
||||
}
|
||||
|
||||
$svc = new ExpertFieldAiService();
|
||||
$result = $svc->linkFromUser($expertId, $force);
|
||||
if (empty($result['ok'])) {
|
||||
return jsonError(isset($result['error']) ? $result['error'] : 'failed');
|
||||
}
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步批量关联
|
||||
* expert_ids: 逗号分隔,或传 limit 扫描待处理前 N 条
|
||||
*/
|
||||
public function linkBatch()
|
||||
{
|
||||
$force = intval($this->request->param('force', 0)) === 1;
|
||||
$idsRaw = trim((string)$this->request->param('expert_ids', ''));
|
||||
$limit = min(max(intval($this->request->param('limit', 0)), 0), 200);
|
||||
|
||||
$ids = [];
|
||||
if ($idsRaw !== '') {
|
||||
$ids = array_filter(array_map('intval', explode(',', $idsRaw)));
|
||||
} elseif ($limit > 0) {
|
||||
$ids = Db::name('expert')
|
||||
->where('state', '<>', 5)
|
||||
->where(function ($q) {
|
||||
$q->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)
|
||||
->whereOr('field_ai_status', ExpertFieldAiService::STATUS_FAILED);
|
||||
})
|
||||
->order('expert_id asc')
|
||||
->limit($limit)
|
||||
->column('expert_id');
|
||||
}
|
||||
|
||||
if (empty($ids)) {
|
||||
return jsonError('expert_ids 或 limit 必填');
|
||||
}
|
||||
|
||||
$svc = new ExpertFieldAiService();
|
||||
$result = $svc->batchLinkFromUser($ids, $force);
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* user 更新 field_ai 后,同步到同邮箱 expert
|
||||
*/
|
||||
public function syncByUser()
|
||||
{
|
||||
$userId = intval($this->request->param('user_id', 0));
|
||||
$force = intval($this->request->param('force', 0)) === 1;
|
||||
if ($userId <= 0) {
|
||||
return jsonError('user_id required');
|
||||
}
|
||||
|
||||
$svc = new ExpertFieldAiService();
|
||||
$result = $svc->syncExpertsByUserId($userId, $force);
|
||||
if (empty($result['ok'])) {
|
||||
return jsonError(isset($result['error']) ? $result['error'] : 'failed');
|
||||
}
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览是否可关联
|
||||
*/
|
||||
public function preview()
|
||||
{
|
||||
$expertId = intval($this->request->param('expert_id', 0));
|
||||
if ($expertId <= 0) {
|
||||
return jsonError('expert_id required');
|
||||
}
|
||||
|
||||
$svc = new ExpertFieldAiService();
|
||||
$result = $svc->previewLink($expertId);
|
||||
if (empty($result['ok'])) {
|
||||
return jsonError(isset($result['error']) ? $result['error'] : 'failed');
|
||||
}
|
||||
|
||||
$result['field_ai_status_text'] = $svc->statusLabel(intval($result['expert_field_ai_status']));
|
||||
return jsonSuccess($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计 field_ai 覆盖
|
||||
*/
|
||||
public function statistics()
|
||||
{
|
||||
$total = Db::name('expert')->where('state', '<>', 5)->count();
|
||||
$done = Db::name('expert')->where('state', '<>', 5)->where('field_ai_status', ExpertFieldAiService::STATUS_DONE)->count();
|
||||
$userLink = Db::name('expert')
|
||||
->where('state', '<>', 5)
|
||||
->where('field_ai_source', ExpertFieldAiService::SOURCE_USER_LINK)
|
||||
->count();
|
||||
$noUserLink = Db::name('expert')
|
||||
->where('state', '<>', 5)
|
||||
->where('field_ai_status', ExpertFieldAiService::STATUS_NO_USER_LINK)
|
||||
->count();
|
||||
$pending = Db::name('expert')
|
||||
->where('state', '<>', 5)
|
||||
->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)
|
||||
->count();
|
||||
|
||||
return jsonSuccess([
|
||||
'total' => $total,
|
||||
'done' => $done,
|
||||
'user_link' => $userLink,
|
||||
'no_user_link' => $noUserLink,
|
||||
'pending' => $pending,
|
||||
'coverage_rate' => $total > 0 ? round($done / $total * 100, 2) . '%' : '0%',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -44,17 +44,22 @@ class ExpertManage extends Base
|
||||
|
||||
$query = Db::name('expert')->alias('e');
|
||||
$countQuery = Db::name('expert')->alias('e');
|
||||
$needJoin = ($field !== '');
|
||||
|
||||
if ($needJoin) {
|
||||
$query->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner');
|
||||
$countQuery->join('t_expert_field ef', 'ef.expert_id = e.expert_id AND ef.state = 0', 'inner');
|
||||
if ($field !== '') {
|
||||
$query->where('ef.field', 'like', '%' . $field . '%');
|
||||
$countQuery->where('ef.field', 'like', '%' . $field . '%');
|
||||
}
|
||||
$query->group('e.expert_id');
|
||||
$countQuery->group('e.expert_id');
|
||||
if ($field !== '') {
|
||||
$fieldExpertIds = Db::name('expert_field')
|
||||
->where('state', 0)
|
||||
->where('field', 'like', '%' . $field . '%')
|
||||
->column('expert_id');
|
||||
$fieldExpertIds = array_values(array_unique(array_filter(array_map('intval', $fieldExpertIds))));
|
||||
|
||||
$fieldWhere = function ($q) use ($field, $fieldExpertIds) {
|
||||
$q->where('e.field_ai', 'like', '%' . $field . '%');
|
||||
if (!empty($fieldExpertIds)) {
|
||||
$q->whereOr('e.expert_id', 'in', $fieldExpertIds);
|
||||
}
|
||||
};
|
||||
$query->where($fieldWhere);
|
||||
$countQuery->where($fieldWhere);
|
||||
}
|
||||
|
||||
if ($state !== '-1' && $state !== '') {
|
||||
@@ -62,8 +67,8 @@ class ExpertManage extends Base
|
||||
$countQuery->where('e.state', intval($state));
|
||||
}
|
||||
if ($keyword !== '') {
|
||||
$query->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%');
|
||||
$countQuery->where('e.name|e.email|e.affiliation', 'like', '%' . $keyword . '%');
|
||||
$query->where('e.name|e.email|e.affiliation|e.field_ai', 'like', '%' . $keyword . '%');
|
||||
$countQuery->where('e.name|e.email|e.affiliation|e.field_ai', 'like', '%' . $keyword . '%');
|
||||
}
|
||||
if ($source !== '') {
|
||||
$query->where('e.source', $source);
|
||||
@@ -72,7 +77,7 @@ class ExpertManage extends Base
|
||||
|
||||
// $countQuery = clone $query;
|
||||
// $total = $countQuery->distinct('e.expert_id')->count();
|
||||
$total = $needJoin ? count($countQuery->group('e.expert_id')->column('e.expert_id')) : $countQuery->count();
|
||||
$total = $countQuery->count();
|
||||
|
||||
$list = $query
|
||||
->field('e.*')
|
||||
|
||||
@@ -2269,7 +2269,8 @@ class Reviewer extends Base
|
||||
$where['t_user.email'] = ['like',"%" . $data["email"] . "%"];
|
||||
}
|
||||
if(isset($data['field'])&&$data['field']!=''){
|
||||
$where['t_user_reviewer_info.field'] = ['like',"%" . $data["field"] . "%"];
|
||||
// field 参数同时匹配原始 field 与 AI 总结 field_ai
|
||||
$where['t_user_reviewer_info.field|t_user_reviewer_info.field_ai'] = ['like',"%" . $data["field"] . "%"];
|
||||
}
|
||||
if (isset($data['major_id'])&&$data['major_id']!=0){
|
||||
$where['t_user_reviewer_info.major'] = ['in',$this->majorids($data['major_id'])];
|
||||
@@ -2306,7 +2307,7 @@ class Reviewer extends Base
|
||||
$list = $this->reviewer_to_journal_obj
|
||||
->join("t_user", "t_user.user_id = t_reviewer_to_journal.reviewer_id", "left")
|
||||
->join("t_user_reviewer_info", "t_user_reviewer_info.reviewer_id = t_reviewer_to_journal.reviewer_id", "left")
|
||||
->field('t_user.account,t_user.email,t_user.realname,t_user_reviewer_info.company,t_user_reviewer_info.field,t_user_reviewer_info.last_invite_time,t_user.user_id,t_user.rs_num')
|
||||
->field('t_user.account,t_user.email,t_user.realname,t_user_reviewer_info.company,t_user_reviewer_info.field,t_user_reviewer_info.field_ai,t_user_reviewer_info.last_invite_time,t_user.user_id,t_user.rs_num')
|
||||
->where($where)->where(function($query) use ($iTeenDaysLater) {
|
||||
$query->where('t_user_reviewer_info.last_invite_time', '<', $iTeenDaysLater)
|
||||
->whereOr('t_user_reviewer_info.last_invite_time', '=', 0);
|
||||
|
||||
Reference in New Issue
Block a user