完善expert领域

This commit is contained in:
wangjinlei
2026-06-05 11:01:16 +08:00
parent 28023be44a
commit 93d25de094
6 changed files with 558 additions and 13 deletions

View 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%',
]);
}
}

View File

@@ -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.*')

View File

@@ -0,0 +1,38 @@
<?php
namespace app\api\job;
use think\queue\Job;
use app\common\ExpertFieldAiService;
/**
* Expert field_ai 链式任务阶段1邮箱关联 user.field_ai
*
* data:
* - expert_id
* - queue 队列名,默认 ExpertFieldAi
* - force 1=强制重算
* - mode link默认
*
* Worker: php think queue:work --queue ExpertFieldAi
*/
class ExpertFieldAiFill
{
public function fire(Job $job, $data)
{
$expertId = isset($data['expert_id']) ? intval($data['expert_id']) : 0;
$queue = isset($data['queue']) ? (string)$data['queue'] : ExpertFieldAiService::QUEUE_NAME;
$force = !empty($data['force']);
$mode = isset($data['mode']) ? (string)$data['mode'] : 'link';
$svc = new ExpertFieldAiService();
if ($expertId > 0 && $mode === 'link') {
$svc->linkFromUser($expertId, $force);
}
$job->delete();
$delay = max(0, (int)(isset($data['delay']) ? $data['delay'] : 1));
$svc->enqueueNextLink($delay, $queue, $expertId, $force);
}
}