本文目录导读:

在PHP项目中实现AI审核结果的人工复核流转,核心思路是将“AI自动判断”与“人工干预”解耦,并通过状态机或工作流引擎来管理审核状态。
以下是实现该功能的完整方案,分为数据结构设计、状态流转逻辑、关键代码实现和UI交互建议四个部分。
数据库表结构设计(核心)
需要两张核心表:audit_records(审核记录表)和audit_workflow_log(审核日志表)。
表1:audit_records(审核记录表)
CREATE TABLE `audit_records` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`target_type` VARCHAR(50) NOT NULL COMMENT '审核对象类型,如:article, comment, image',
`target_id` INT UNSIGNED NOT NULL COMMENT '审核对象ID',
`content` TEXT COMMENT '原始内容摘要,方便人工查看',
// AI 审核结果
`ai_result` ENUM('pass', 'reject', 'review') DEFAULT 'review' COMMENT 'AI判定结果:通过/拒绝/疑似需人工审核',
`ai_reason` VARCHAR(500) DEFAULT NULL COMMENT 'AI驳回原因或风险标签',
`ai_score` DECIMAL(5,2) DEFAULT NULL COMMENT 'AI置信度分数,用于辅助判断',
// 流转控制字段(关键)
`status` ENUM('pending_ai', 'pending_review', 'passed', 'rejected') DEFAULT 'pending_ai' COMMENT '当前审核状态',
`assigned_to` INT UNSIGNED DEFAULT NULL COMMENT '当前审核人ID(抢单/分配)',
`assigned_at` DATETIME DEFAULT NULL COMMENT '分配时间',
`reviewed_by` INT UNSIGNED DEFAULT NULL COMMENT '最终复核人ID',
`review_result` ENUM('pass', 'reject') DEFAULT NULL COMMENT '人工最终结果',
`review_reason` TEXT COMMENT '人工备注',
// 时间戳
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX `idx_target` (`target_type`, `target_id`),
INDEX `idx_status` (`status`),
INDEX `idx_assigned_to` (`assigned_to`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
表2:audit_workflow_log(审核流转日志)
CREATE TABLE `audit_workflow_log` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`record_id` INT UNSIGNED NOT NULL,
`from_status` VARCHAR(50) DEFAULT NULL,
`to_status` VARCHAR(50) NOT NULL,
`action` VARCHAR(100) NOT NULL COMMENT '操作:ai_auto / assign / review_pass / review_reject / recall',
`operator_id` INT UNSIGNED DEFAULT NULL COMMENT '操作人ID(null表示系统)',
`operator_name` VARCHAR(100) DEFAULT NULL,
`remark` TEXT COMMENT '操作备注',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_record` (`record_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
状态流转设计
设计一个有限状态机(FSM),避免乱序操作,推荐使用枚举或常量类管理:
| 当前状态 | 可执行动作 | 下一状态 | 说明 |
|---|---|---|---|
pending_ai |
AI 审核完成 | pending_review(AI 结果为 review) |
AI 认为需要人工复核 |
pending_ai |
AI 审核完成 | passed(AI 结果为 pass) |
AI 直接通过(可选) |
pending_ai |
AI 审核完成 | rejected(AI 结果为 reject) |
AI 直接拒绝(可选) |
pending_review |
人工复核通过 | passed |
核心流转点 |
pending_review |
人工复核拒绝 | rejected |
核心流转点 |
pending_review |
手动分配/抢单 | pending_review (更新assigned_to) |
分配审核人 |
passed |
撤回操作(如果需要) | pending_review |
特殊情况,如误判 |
rejected |
撤回操作(如果需要) | pending_review |
特殊情况 |
PHP 核心逻辑实现
建议封装一个单独的“审核工作流服务类”。
<?php
// app/Services/AuditWorkflowService.php
class AuditWorkflowService
{
// 定义允许的状态转换
private array $transitions = [
'pending_ai' => ['pending_review', 'passed', 'rejected'],
'pending_review' => ['passed', 'rejected'],
'passed' => ['pending_review'], // 撤回
'rejected' => ['pending_review'], // 撤回
];
/**
* 执行状态变更(核心方法)
* @param int $recordId 审核记录ID
* @param string $action 操作动作
* @param int|null $operatorId 操作人ID
* @param string|null $remark 备注
* @param array $extraData 额外数据(如 review_result, review_reason)
* @return bool
* @throws \Exception
*/
public function transition(int $recordId, string $action, ?int $operatorId = null, ?string $remark = null, array $extraData = []): bool
{
DB::beginTransaction();
try {
$record = AuditRecord::lockForUpdate()->find($recordId);
if (!$record) throw new \Exception('审核记录不存在');
$currentStatus = $record->status;
// 根据action 计算出目标状态
$newStatus = $this->getNextStatus($currentStatus, $action);
// 校验转换是否合法 (防止直接修改数据库)
if (!in_array($newStatus, $this->transitions[$currentStatus] ?? [])) {
throw new \Exception("不允许从 {$currentStatus} 转换到 {$newStatus}");
}
// 特殊校验:分配操作
if ($action === 'assign') {
$this->assignHandler($record, $extraData['assigned_to'] ?? 0);
}
// 更新主记录
$updateData = [
'status' => $newStatus,
'updated_at' => now(),
];
// 如果是人工最终审核(通过/拒绝),写入人工结果
if (in_array($newStatus, ['passed', 'rejected'])) {
$updateData['reviewed_by'] = $operatorId;
$updateData['review_result'] = ($newStatus === 'passed') ? 'pass' : 'reject';
$updateData['review_reason'] = $remark;
}
$record->update($updateData);
// 写流转日志
$this->logTransition($recordId, $currentStatus, $newStatus, $action, $operatorId, $remark);
DB::commit();
return true;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
private function getNextStatus(string $currentStatus, string $action): string
{
// 根据动作映射新状态
$map = [
'pass' => 'passed',
'reject' => 'rejected',
'assign' => 'pending_review', // 分配后依然是待审状态
'recall' => 'pending_review', // 撤回
];
return $map[$action] ?? $currentStatus;
}
private function assignHandler($record, int $assignedTo): void
{
// 这里可以增加并发控制,防止多人同时抢同一单
if ($record->assigned_to && $record->assigned_to != $assignedTo) {
throw new \Exception('该记录已被其他审核员认领');
}
$record->update([
'assigned_to' => $assignedTo,
'assigned_at' => now(),
]);
}
private function logTransition($recordId, $fromStatus, $toStatus, $action, $operatorId, $remark): void
{
AuditWorkflowLog::create([
'record_id' => $recordId,
'from_status' => $fromStatus,
'to_status' => $toStatus,
'action' => $action,
'operator_id' => $operatorId,
'operator_name' => $operatorId ? User::find($operatorId)->name : 'system',
'remark' => $remark,
]);
}
// --- 对外API ---
/**
* 人工审核通过
*/
public function reviewPass(int $recordId, int $operatorId, string $reason = '')
{
return $this->transition($recordId, 'pass', $operatorId, $reason);
}
/**
* 人工审核拒绝
*/
public function reviewReject(int $recordId, int $operatorId, string $reason = '')
{
return $this->transition($recordId, 'reject', $operatorId, $reason);
}
/**
* 认领/分配
*/
public function assign(int $recordId, int $operatorId)
{
return $this->transition($recordId, 'assign', $operatorId, '认领任务', ['assigned_to' => $operatorId]);
}
}
AI 审核回调(触发入口)
在你的AI回调逻辑中,调用工作流服务。
// 假设在AI审核回调中
public function handleAiCallback($targetType, $targetId, $aiResult, $aiReason, $aiScore)
{
// 1. 查找或创建审核记录
$record = AuditRecord::firstOrCreate(
['target_type' => $targetType, 'target_id' => $targetId],
['content' => '...', 'status' => 'pending_ai']
);
// 2. 写入AI结果
$record->update([
'ai_result' => $aiResult,
'ai_reason' => $aiReason,
'ai_score' => $aiScore,
]);
// 3. 根据AI结果决定下一步流转
$workflow = new AuditWorkflowService();
switch ($aiResult) {
case 'pass':
$workflow->transition($record->id, 'auto_pass', null, 'AI自动通过, 分数:'.$aiScore);
break;
case 'reject':
$workflow->transition($record->id, 'auto_reject', null, 'AI自动拒绝:'.$aiReason);
break;
case 'review':
// 这里可以触发消息通知,提醒审核员有新任务
$workflow->transition($record->id, 'need_review', null, 'AI判定需人工复审');
break;
}
}
用户界面交互建议
- 待审列表:根据
status = 'pending_review'过滤,列表显示 AI 评分、AI 判定原因(黄/红色标签提示),方便人工优先级排序。 - 详情页面:
- 顶部:展示
AI Result(绿色/红色徽章)和AI Reason。 - 中间:展示原始内容(文本/图片)。
- 底部:审核操作按钮(通过 / 拒绝),并强制填写理由。
- 顶部:展示
- 抢单机制:可以加一个
assigned_to字段,不为空的记录在其他人列表中不显示,利用数据库事务 + 行锁防止重复分配。 - 批量处理:在列表页提供多选框 + 批量通过/拒绝(注意:批量操作也要走循环调用
transition,因为事务和锁的开销)。 - 撤回功能:仅开放给超级管理员,用于纠正误操作。
注意事项(坑点)
- 并发问题:审核员可能同时点击“通过”,日志里的
lockForUpdate()是必要的,防止状态错乱。 - 消息通知:当 AI 标记为
review时,应通过 WebSocket 或消息队列通知相关负责人。 - 批量操作:不要直接用
update()跳过日志,即使批量通过/拒绝,也应该逐条调用transition记录日志。 - 撤回风险:被撤回的记录,内容中应保留原始 AI 和人工的审核历史,不能丢失。
- 可视化流程图:如果能用 laravel-workflow 或类似库画一个流程图更好,但一般的 PHPer 直接用上面状态机足够了。
方案的核心是:status 字段 + audit_workflow_log 日志 + lockForUpdate 并发控制,通过简单的 switch-case 或 状态数组 来定义流转规则,可维护性高,不易出错。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播