This commit is contained in:
wyn
2026-06-08 11:01:17 +08:00
4 changed files with 537 additions and 149 deletions

View File

@@ -6,39 +6,81 @@ use think\Db;
use app\common\ExpertFieldAiService;
/**
* Expert 领域总结(方案 C - 阶段1邮箱关联 user.field_ai
* Expert 领域 AI 总结(方案 C:少量 user 关联 + 主流程 AI
*
* POST startLinkChain 启动链式队列,批量关联
* POST linkOne 同步关联单个 expert_id
* POST linkBatch 同步批量关联 expert_ids
* POST syncByUser user 有 field_ai 后,同步到同邮箱 expert
* GET preview 预览是否可关联
* GET statistics 统计 field_ai 覆盖情况
* POST startChain 启动链式队列(关联 + AI主入口
* POST processOne 同步处理单个 expert_id
* POST processBatch 同步批量处理
* POST linkOne user 关联(调试)
* POST syncByUser user 有 field_ai 后同步到 expert
* GET preview 预览可关联 / 可 AI 总结 / 上下文
* GET statistics 覆盖统计
*/
class ExpertFieldAi extends Base
{
/**
* 启动链式关联队列
* 启动链式处理(主入口)
* Worker: php think queue:work --queue ExpertFieldAi
*/
public function startLinkChain()
public function startChain()
{
$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);
$started = $svc->startChain($force, $delay);
return jsonSuccess([
'started' => $started,
'queue' => ExpertFieldAiService::QUEUE_NAME,
'force' => $force,
'msg' => $started ? 'link chain enqueued' : 'no pending experts',
'msg' => $started ? 'chain enqueued' : 'no pending experts',
]);
}
/** 兼容旧接口名 */
public function startLinkChain()
{
return $this->startChain();
}
/**
* 同步关联单个 expert
* 同步处理单个 expert(关联 + AI
*/
public function processOne()
{
$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->processExpert($expertId, $force);
if (empty($result['ok'])) {
return jsonError(isset($result['error']) ? $result['error'] : 'failed');
}
return jsonSuccess($result);
}
/**
* 同步批量处理
* expert_ids 逗号分隔,或 limit 扫描待处理前 N 条
*/
public function processBatch()
{
$force = intval($this->request->param('force', 0)) === 1;
$ids = $this->resolveExpertIds();
if (empty($ids)) {
return jsonError('expert_ids 或 limit 必填');
}
$svc = new ExpertFieldAiService();
return jsonSuccess($svc->batchProcess($ids, $force));
}
/**
* 仅 user 关联(不 AI
*/
public function linkOne()
{
@@ -56,43 +98,18 @@ class ExpertFieldAi extends Base
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');
}
$ids = $this->resolveExpertIds(true);
if (empty($ids)) {
return jsonError('expert_ids 或 limit 必填');
}
$svc = new ExpertFieldAiService();
$result = $svc->batchLinkFromUser($ids, $force);
return jsonSuccess($result);
return jsonSuccess($svc->batchLinkFromUser($ids, $force));
}
/**
* user 更新 field_ai 后,同步到同邮箱 expert
*/
public function syncByUser()
{
$userId = intval($this->request->param('user_id', 0));
@@ -110,7 +127,7 @@ class ExpertFieldAi extends Base
}
/**
* 预览是否可关联
* 预览是否可 user 关联、是否可 AI、上下文摘要
*/
public function preview()
{
@@ -120,7 +137,7 @@ class ExpertFieldAi extends Base
}
$svc = new ExpertFieldAiService();
$result = $svc->previewLink($expertId);
$result = $svc->preview($expertId);
if (empty($result['ok'])) {
return jsonError(isset($result['error']) ? $result['error'] : 'failed');
}
@@ -129,33 +146,58 @@ class ExpertFieldAi extends Base
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();
$base = Db::name('expert')->where('state', '<>', 5);
$total = (clone $base)->count();
$done = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_DONE)->count();
$userLink = (clone $base)->where('field_ai_source', ExpertFieldAiService::SOURCE_USER_LINK)->count();
$aiDone = (clone $base)->where('field_ai_source', ExpertFieldAiService::SOURCE_AI)->count();
$pending = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)->count();
$noUserLink = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_NO_USER_LINK)->count();
$insufficient = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_INSUFFICIENT)->count();
$failed = (clone $base)->where('field_ai_status', ExpertFieldAiService::STATUS_FAILED)->count();
return jsonSuccess([
'total' => $total,
'done' => $done,
'user_link' => $userLink,
'no_user_link' => $noUserLink,
'ai_done' => $aiDone,
'pending' => $pending,
'no_user_link' => $noUserLink,
'insufficient' => $insufficient,
'failed' => $failed,
'coverage_rate' => $total > 0 ? round($done / $total * 100, 2) . '%' : '0%',
]);
}
private function resolveExpertIds($linkOnly = false)
{
$idsRaw = trim((string)$this->request->param('expert_ids', ''));
$limit = min(max(intval($this->request->param('limit', 0)), 0), 200);
if ($idsRaw !== '') {
return array_filter(array_map('intval', explode(',', $idsRaw)));
}
if ($limit <= 0) {
return [];
}
$query = Db::name('expert')->where('state', '<>', 5);
if ($linkOnly) {
$query->where(function ($q) {
$q->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)
->whereOr('field_ai_status', ExpertFieldAiService::STATUS_FAILED);
});
} else {
$query->where(function ($q) {
$q->where('field_ai_status', ExpertFieldAiService::STATUS_PENDING)
->whereOr('field_ai_status', ExpertFieldAiService::STATUS_FAILED)
->whereOr('field_ai_status', ExpertFieldAiService::STATUS_NO_USER_LINK);
});
}
return $query->order('expert_id asc')->limit($limit)->column('expert_id');
}
}