204 lines
6.9 KiB
PHP
204 lines
6.9 KiB
PHP
<?php
|
||
|
||
namespace app\api\controller;
|
||
|
||
use think\Db;
|
||
use app\common\ExpertFieldAiService;
|
||
|
||
/**
|
||
* Expert 领域 AI 总结(方案 C:少量 user 关联 + 主流程 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 startChain()
|
||
{
|
||
$force = intval($this->request->param('force', 0)) === 1;
|
||
$delay = max(0, intval($this->request->param('delay', 1)));
|
||
|
||
$svc = new ExpertFieldAiService();
|
||
$started = $svc->startChain($force, $delay);
|
||
|
||
return jsonSuccess([
|
||
'started' => $started,
|
||
'queue' => ExpertFieldAiService::QUEUE_NAME,
|
||
'force' => $force,
|
||
'msg' => $started ? 'chain enqueued' : 'no pending experts',
|
||
]);
|
||
}
|
||
|
||
/** 兼容旧接口名 */
|
||
public function startLinkChain()
|
||
{
|
||
return $this->startChain();
|
||
}
|
||
|
||
/**
|
||
* 同步处理单个 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()
|
||
{
|
||
$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);
|
||
}
|
||
|
||
public function linkBatch()
|
||
{
|
||
$force = intval($this->request->param('force', 0)) === 1;
|
||
$ids = $this->resolveExpertIds(true);
|
||
if (empty($ids)) {
|
||
return jsonError('expert_ids 或 limit 必填');
|
||
}
|
||
|
||
$svc = new ExpertFieldAiService();
|
||
return jsonSuccess($svc->batchLinkFromUser($ids, $force));
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* 预览:是否可 user 关联、是否可 AI、上下文摘要
|
||
*/
|
||
public function preview()
|
||
{
|
||
$expertId = intval($this->request->param('expert_id', 0));
|
||
if ($expertId <= 0) {
|
||
return jsonError('expert_id required');
|
||
}
|
||
|
||
$svc = new ExpertFieldAiService();
|
||
$result = $svc->preview($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);
|
||
}
|
||
|
||
public function statistics()
|
||
{
|
||
$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,
|
||
'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');
|
||
}
|
||
}
|