dootask/app/Tasks/AiTaskAnalyzeTask.php
kuaifan 75073d4320 fix(ai): address security and robustness issues from code review
Security fixes:
- Add escapeUserInput() to prevent Prompt injection via user input
- Validate msgId belongs to dialogId in updateMessageStatus()
- Add type parameter whitelist validation in ai-apply/ai-dismiss
- Add event record validation in task__ai_dismiss

Robustness fixes:
- Use atomic update for markProcessing to prevent concurrent processing
- Add subtask count limit check before creation (max 50)
- Disable similar task feature until vector search is implemented
- Fix Promise anti-pattern in frontend actions

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-01-21 15:30:06 +00:00

132 lines
4.0 KiB
PHP

<?php
namespace App\Tasks;
use App\Models\ProjectTask;
use App\Models\ProjectTaskAiEvent;
use App\Module\AiTaskSuggestion;
use App\Module\Base;
/**
* AI 任务分析异步任务
* 处理单个任务的所有 AI 事件
*/
class AiTaskAnalyzeTask extends AbstractTask
{
protected int $taskId;
public function __construct(int $taskId)
{
parent::__construct();
$this->taskId = $taskId;
}
public function start()
{
$task = ProjectTask::with('project')->find($this->taskId);
if (!$task || $task->deleted_at || !$task->dialog_id) {
return;
}
// 获取该任务的所有待处理事件
$events = ProjectTaskAiEvent::where('task_id', $this->taskId)
->whereIn('status', [
ProjectTaskAiEvent::STATUS_PENDING,
ProjectTaskAiEvent::STATUS_FAILED,
])
->get()
->keyBy('event_type');
$suggestions = [];
// 遍历所有事件类型
foreach (ProjectTaskAiEvent::getEventTypes() as $eventType) {
$event = $events->get($eventType);
// 如果没有记录,跳过
if (!$event) {
continue;
}
// 如果是失败状态但不能重试,跳过
if ($event->status === ProjectTaskAiEvent::STATUS_FAILED && !$event->canRetry()) {
continue;
}
// 使用原子操作标记为处理中(防止并发重复处理)
$updated = ProjectTaskAiEvent::where('id', $event->id)
->whereIn('status', [ProjectTaskAiEvent::STATUS_PENDING, ProjectTaskAiEvent::STATUS_FAILED])
->update(['status' => ProjectTaskAiEvent::STATUS_PROCESSING]);
if (!$updated) {
// 已被其他进程处理
continue;
}
$event->status = ProjectTaskAiEvent::STATUS_PROCESSING;
try {
// 检查是否满足执行条件
if (!AiTaskSuggestion::shouldExecute($task, $eventType)) {
$event->markSkipped('不满足执行条件');
continue;
}
// 执行对应的分析
$result = $this->executeAnalysis($task, $eventType);
if ($result === null) {
$event->markSkipped('未生成有效建议');
continue;
}
// 收集建议
$suggestions[] = $result;
$event->markCompleted($result);
} catch (\Exception $e) {
$event->markFailed($e->getMessage());
\Log::error("AiTaskAnalyzeTask error: task={$this->taskId}, type={$eventType}, error={$e->getMessage()}");
}
}
// 如果有建议,发送消息
if (!empty($suggestions)) {
$msgId = AiTaskSuggestion::sendSuggestionMessage($task, $suggestions);
// 更新所有事件的 msg_id
if ($msgId) {
ProjectTaskAiEvent::where('task_id', $this->taskId)
->where('status', ProjectTaskAiEvent::STATUS_COMPLETED)
->update(['msg_id' => $msgId]);
}
}
}
/**
* 执行具体的分析
*/
private function executeAnalysis(ProjectTask $task, string $eventType): ?array
{
switch ($eventType) {
case ProjectTaskAiEvent::EVENT_DESCRIPTION:
return AiTaskSuggestion::generateDescription($task);
case ProjectTaskAiEvent::EVENT_SUBTASKS:
return AiTaskSuggestion::generateSubtasks($task);
case ProjectTaskAiEvent::EVENT_ASSIGNEE:
return AiTaskSuggestion::generateAssignee($task);
case ProjectTaskAiEvent::EVENT_SIMILAR:
return AiTaskSuggestion::findSimilarTasks($task);
default:
return null;
}
}
public function end()
{
}
}