refactor: remove syncing

This commit is contained in:
2026-03-16 13:56:10 +08:00
parent 58f42de9df
commit 8d30a0419d
18 changed files with 25 additions and 954 deletions

View File

@@ -1,71 +0,0 @@
<?php
namespace App\Filament\Actions;
use App\Models\Terminal;
use App\Services\TerminalSyncService;
use Filament\Tables\Actions\Action;
use Filament\Tables\Actions\BulkAction;
use Filament\Notifications\Notification;
/**
* 同步终端配置Action
*/
class SyncConfigAction
{
/**
* 创建单个终端同步Action
*
* @return Action
*/
public static function make(): Action
{
return Action::make('sync')
->label('同步配置')
->icon('heroicon-o-arrow-path')
->color('success')
->requiresConfirmation()
->modalHeading('同步终端配置')
->modalDescription('确定要将配置同步到此终端吗?')
->modalSubmitActionLabel('确认同步')
->action(function (Terminal $record) {
$service = app(TerminalSyncService::class);
$log = $service->syncConfiguration($record);
Notification::make()
->title('同步任务已启动')
->body("终端 {$record->name} 的配置同步任务已加入队列")
->success()
->send();
});
}
/**
* 创建批量同步Action
*
* @return BulkAction
*/
public static function makeBulk(): BulkAction
{
return BulkAction::make('batchSync')
->label('批量同步')
->icon('heroicon-o-arrow-path')
->color('success')
->requiresConfirmation()
->modalHeading('批量同步终端配置')
->modalDescription(fn ($records) => "确定要同步 {$records->count()} 个终端的配置吗?")
->modalSubmitActionLabel('确认同步')
->action(function ($records) {
$service = app(TerminalSyncService::class);
$terminalIds = $records->pluck('id')->toArray();
$logs = $service->batchSync($terminalIds);
Notification::make()
->title('批量同步任务已启动')
->body("已为 " . count($logs) . " 个终端创建同步任务")
->success()
->send();
})
->deselectRecordsAfterCompletion();
}
}

View File

@@ -3,7 +3,6 @@
namespace App\Filament\Resources;
use App\Filament\Resources\TerminalResource\Pages;
use App\Filament\Actions\SyncConfigAction;
use App\Models\Terminal;
use Filament\Forms;
use Filament\Forms\Form;
@@ -86,50 +85,34 @@ class TerminalResource extends Resource
])
->columns(2),
Forms\Components\Section::make('组态配置')
Forms\Components\Section::make('组态配置')
->schema([
Forms\Components\TextInput::make('diagram_url')
->label('组态图URL')
->label('组态界面地址')
->url()
->maxLength(500)
->placeholder('https://example.com/diagram.png')
->helperText('组态的访问地址'),
->helperText('组态界面的访问地址'),
]),
Forms\Components\Section::make('SCADA网关配置')
Forms\Components\Section::make('网关配置')
->schema([
Forms\Components\TextInput::make('scada_data_url')
->label('SCADA数据查询URL')
->label('数据查询URL')
->url()
->maxLength(500)
->placeholder('http://gateway:8080/api/data')
->helperText('OPC UA HTTP网关的数据查询地址'),
->helperText('网关的数据查询地址'),
Forms\Components\TextInput::make('scada_tags_url')
->label('SCADA点位定义URL')
->label('点位定义URL')
->url()
->maxLength(500)
->placeholder('http://gateway:8080/api/tags')
->helperText('OPC UA HTTP网关的点位定义查询地址'),
->helperText('网关的点位定义查询地址'),
])
->columns(2),
Forms\Components\Section::make('显示配置')
->schema([
Forms\Components\KeyValue::make('display_config')
->label('显示参数')
->keyLabel('参数名称')
->valueLabel('参数值')
->addActionLabel('添加参数')
->helperText('配置终端的显示参数,如分辨率、刷新率等')
->default([
'resolution' => '1920x1080',
'refresh_rate' => '60',
'orientation' => 'landscape',
'brightness' => '80',
]),
]),
Forms\Components\Section::make('知识库关联')
->schema([
Forms\Components\Repeater::make('knowledgeBaseAssociations')
@@ -158,7 +141,8 @@ class TerminalResource extends Resource
->reorderableWithButtons()
->addActionLabel('添加知识库')
->reorderableWithDragAndDrop(false)
->itemLabel(fn (array $state): ?string =>
->itemLabel(
fn(array $state): ?string =>
\App\Models\KnowledgeBase::find($state['id'])?->name ?? '未选择'
)
->collapsed()
@@ -195,7 +179,8 @@ class TerminalResource extends Resource
->reorderableWithButtons()
->addActionLabel('添加指引')
->reorderableWithDragAndDrop(false)
->itemLabel(fn (array $state): ?string =>
->itemLabel(
fn(array $state): ?string =>
\App\Models\Guide::find($state['id'])?->name ?? '未选择'
)
->collapsed()
@@ -216,16 +201,16 @@ class TerminalResource extends Resource
->placeholderText('请输入AI提示词模板...')
->disablePreview()
->columnSpan(2),
Forms\Components\Grid::make(1)
->schema([
Forms\Components\Placeholder::make('template_selector')
->label('模板库')
->content(fn () => view('filament.components.prompt-template-selector')),
->content(fn() => view('filament.components.prompt-template-selector')),
Forms\Components\Placeholder::make('variable_helper')
->label('变量参考')
->content(fn () => view('filament.components.prompt-variable-helper')),
->content(fn() => view('filament.components.prompt-variable-helper')),
])
->columnSpan(1),
]),
@@ -296,33 +281,6 @@ class TerminalResource extends Resource
->falseColor('danger')
->sortable(),
Tables\Columns\TextColumn::make('latestSyncLog.status')
->label('同步状态')
->badge()
->formatStateUsing(fn (string $state): string => match ($state) {
'pending' => '待同步',
'syncing' => '同步中',
'synced' => '已同步',
'failed' => '失败',
default => '未知',
})
->color(fn (string $state): string => match ($state) {
'pending' => 'warning',
'syncing' => 'info',
'synced' => 'success',
'failed' => 'danger',
default => 'gray',
})
->icon(fn (string $state): string => match ($state) {
'pending' => 'heroicon-o-clock',
'syncing' => 'heroicon-o-arrow-path',
'synced' => 'heroicon-o-check-circle',
'failed' => 'heroicon-o-x-circle',
default => 'heroicon-o-question-mark-circle',
})
->placeholder('从未同步')
->sortable(),
Tables\Columns\TextColumn::make('last_online_at')
->label('最后在线时间')
->dateTime('Y-m-d H:i:s')
@@ -348,17 +306,8 @@ class TerminalResource extends Resource
->placeholder('全部')
->trueLabel('在线')
->falseLabel('离线'),
Tables\Filters\Filter::make('station_id')
->label('已绑定线站')
->query(fn (Builder $query): Builder => $query->whereNotNull('station_id')),
Tables\Filters\Filter::make('has_diagram')
->label('已配置组态图')
->query(fn (Builder $query): Builder => $query->whereNotNull('diagram_url')),
])
->actions([
SyncConfigAction::make(),
Tables\Actions\ViewAction::make()
->label('查看'),
Tables\Actions\EditAction::make()
@@ -368,7 +317,6 @@ class TerminalResource extends Resource
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
SyncConfigAction::makeBulk(),
Tables\Actions\DeleteBulkAction::make()
->label('批量删除'),
]),
@@ -380,7 +328,7 @@ class TerminalResource extends Resource
->collapsible(),
Tables\Grouping\Group::make('is_online')
->label('按在线状态分组')
->getTitleFromRecordUsing(fn (Terminal $record): string => $record->is_online ? '在线' : '离线')
->getTitleFromRecordUsing(fn(Terminal $record): string => $record->is_online ? '在线' : '离线')
->collapsible(),
]);
}

View File

@@ -43,24 +43,16 @@ class ViewTerminal extends ViewRecord
])
->columns(2),
Infolists\Components\Section::make('组态配置')
Infolists\Components\Section::make('组态配置')
->schema([
Infolists\Components\TextEntry::make('diagram_url')
->label('组态图URL')
->label('组态界面地址')
->copyable()
->placeholder('未设置')
->url(fn ($state) => $state)
->url(fn($state) => $state)
->openUrlInNewTab(),
]),
Infolists\Components\Section::make('显示配置')
->schema([
Infolists\Components\KeyValueEntry::make('display_config')
->label('显示参数')
->keyLabel('参数名称')
->valueLabel('参数值'),
]),
Infolists\Components\Section::make('知识库关联')
->schema([
Infolists\Components\RepeatableEntry::make('knowledgeBases')
@@ -103,49 +95,6 @@ class ViewTerminal extends ViewRecord
])
->columns(2),
Infolists\Components\Section::make('同步历史')
->schema([
Infolists\Components\RepeatableEntry::make('syncLogs')
->label('同步记录')
->schema([
Infolists\Components\TextEntry::make('status')
->label('状态')
->badge()
->formatStateUsing(fn (string $state): string => match ($state) {
'pending' => '待同步',
'syncing' => '同步中',
'synced' => '已同步',
'failed' => '失败',
default => '未知',
})
->color(fn (string $state): string => match ($state) {
'pending' => 'warning',
'syncing' => 'info',
'synced' => 'success',
'failed' => 'danger',
default => 'gray',
}),
Infolists\Components\TextEntry::make('created_at')
->label('创建时间')
->dateTime('Y-m-d H:i:s'),
Infolists\Components\TextEntry::make('synced_at')
->label('同步完成时间')
->dateTime('Y-m-d H:i:s')
->placeholder('未完成'),
Infolists\Components\TextEntry::make('error_message')
->label('错误信息')
->placeholder('无')
->color('danger')
->columnSpanFull(),
])
->columns(3)
->placeholder('暂无同步记录')
->contained(false),
])
->description('显示最近的配置同步记录,按时间倒序排列')
->collapsible()
->collapsed(),
Infolists\Components\Section::make('时间信息')
->schema([
Infolists\Components\TextEntry::make('created_at')

View File

@@ -5,7 +5,6 @@ namespace App\Filament\Widgets;
use App\Models\Terminal;
use App\Models\TerminalKnowledgeBase;
use App\Models\TerminalPrompt;
use App\Models\TerminalSyncLog;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
@@ -25,15 +24,6 @@ class TerminalStatsWidget extends BaseWidget
// 统计提示词
$totalPrompts = TerminalPrompt::count();
// 统计最近同步
$recentSyncs = TerminalSyncLog::where('created_at', '>=', now()->subDay())
->where('status', 'success')
->count();
$failedSyncs = TerminalSyncLog::where('created_at', '>=', now()->subDay())
->where('status', 'failed')
->count();
return [
Stat::make('终端总数', $totalTerminals)
->description("{$onlineTerminals} 个在线")
@@ -50,16 +40,6 @@ class TerminalStatsWidget extends BaseWidget
->description('终端提示词总数')
->descriptionIcon('heroicon-m-chat-bubble-left-right')
->color('success'),
Stat::make('今日同步成功', $recentSyncs)
->description('最近24小时')
->descriptionIcon('heroicon-m-arrow-path')
->color('success'),
Stat::make('今日同步失败', $failedSyncs)
->description($failedSyncs > 0 ? '需要检查' : '运行正常')
->descriptionIcon($failedSyncs > 0 ? 'heroicon-m-exclamation-triangle' : 'heroicon-m-check-circle')
->color($failedSyncs > 0 ? 'danger' : 'success'),
];
}
}

View File

@@ -47,7 +47,6 @@ class TerminalApiController extends Controller
'diagram_url' => $terminal->diagram_url,
'scada_data_url' => $terminal->scada_data_url,
'scada_tags_url' => $terminal->scada_tags_url,
'display_config' => $terminal->display_config,
],
'system_prompt' => $systemPrompt,
'guide_count' => $guideCount,

View File

@@ -1,160 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Terminal;
use App\Models\TerminalSyncLog;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* 终端配置同步任务
*/
class SyncTerminalConfigJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数
*
* @var int
*/
public $tries = 3;
/**
* 任务超时时间(秒)
*
* @var int
*/
public $timeout = 60;
/**
* 创建新的任务实例
*
* @param Terminal $terminal
* @param TerminalSyncLog $log
*/
public function __construct(
public Terminal $terminal,
public TerminalSyncLog $log
) {}
/**
* 执行任务
*
* @return void
*/
public function handle(): void
{
try {
// 更新状态为同步中
$this->log->update(['status' => 'syncing']);
Log::info('开始同步终端配置', [
'terminal_id' => $this->terminal->id,
'terminal_name' => $this->terminal->name,
'log_id' => $this->log->id,
]);
// 模拟同步过程因为没有真实的终端API
// 在实际环境中这里应该调用真实的终端API
$this->simulateSync();
// 更新状态为已同步
$this->log->update([
'status' => 'synced',
'synced_at' => now(),
'error_message' => null,
]);
Log::info('终端配置同步成功', [
'terminal_id' => $this->terminal->id,
'log_id' => $this->log->id,
]);
} catch (\Exception $e) {
// 记录错误日志
Log::error('终端配置同步失败', [
'terminal_id' => $this->terminal->id,
'log_id' => $this->log->id,
'error' => $e->getMessage(),
'attempt' => $this->attempts(),
]);
// 更新状态为失败
$this->log->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
]);
// 如果还有重试次数,则重新抛出异常以触发重试
if ($this->attempts() < $this->tries) {
throw $e;
}
}
}
/**
* 模拟同步过程
*
* @return void
* @throws \Exception
*/
private function simulateSync(): void
{
// 模拟网络延迟
sleep(2);
// 随机模拟成功或失败90%成功率)
if (rand(1, 100) <= 10) {
throw new \Exception('模拟同步失败:网络连接超时');
}
// 在实际环境中,这里应该是类似这样的代码:
// $response = Http::timeout(30)
// ->post($this->terminal->sync_url, [
// 'config' => $this->log->config_snapshot,
// ]);
//
// if (!$response->successful()) {
// throw new \Exception('同步失败:' . $response->body());
// }
}
/**
* 任务失败时的处理
*
* @param \Throwable $exception
* @return void
*/
public function failed(\Throwable $exception): void
{
Log::error('终端配置同步任务最终失败', [
'terminal_id' => $this->terminal->id,
'log_id' => $this->log->id,
'error' => $exception->getMessage(),
]);
// 确保状态更新为失败
$this->log->update([
'status' => 'failed',
'error_message' => '同步失败(已达最大重试次数):' . $exception->getMessage(),
]);
}
/**
* 计算重试延迟时间(秒)
*
* @return int
*/
public function backoff(): int
{
// 指数退避第1次重试等待10秒第2次等待30秒
return [10, 30][$this->attempts() - 1] ?? 60;
}
}

View File

@@ -26,7 +26,6 @@ class Terminal extends Model
'diagram_url',
'scada_data_url',
'scada_tags_url',
'display_config',
'is_online',
'last_online_at',
];
@@ -39,7 +38,6 @@ class Terminal extends Model
protected function casts(): array
{
return [
'display_config' => 'array',
'is_online' => 'boolean',
'last_online_at' => 'datetime',
];
@@ -81,26 +79,6 @@ class Terminal extends Model
return $this->hasOne(TerminalPrompt::class);
}
/**
* 获取终端的同步日志
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function syncLogs()
{
return $this->hasMany(TerminalSyncLog::class)->orderBy('created_at', 'desc');
}
/**
* 获取终端的最新同步日志
*
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function latestSyncLog()
{
return $this->hasOne(TerminalSyncLog::class)->latestOfMany();
}
/**
* 配置活动日志选项
*
@@ -109,7 +87,7 @@ class Terminal extends Model
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'code', 'station_id', 'diagram_url', 'display_config'])
->logOnly(['name', 'code', 'station_id', 'diagram_url'])
->logOnlyDirty()
->setDescriptionForEvent(fn(string $eventName) => "终端已{$eventName}");
}

View File

@@ -1,44 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class TerminalSyncLog extends Model
{
/**
* 可批量赋值的属性
*
* @var array<string>
*/
protected $fillable = [
'terminal_id',
'status',
'config_snapshot',
'synced_at',
'error_message',
];
/**
* 属性类型转换
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
'config_snapshot' => 'array',
'synced_at' => 'datetime',
];
}
/**
* 获取同步日志所属的终端
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function terminal()
{
return $this->belongsTo(Terminal::class);
}
}

View File

@@ -65,18 +65,6 @@ class TerminalPolicy
return $user->can('terminal.delete');
}
/**
* 判断用户是否可以同步终端配置
*
* @param User $user
* @param Terminal $terminal
* @return bool
*/
public function sync(User $user, Terminal $terminal): bool
{
return $user->can('terminal.sync');
}
/**
* 判断用户是否可以恢复已删除的终端
*

View File

@@ -1,112 +0,0 @@
<?php
namespace App\Services;
use App\Models\Terminal;
use App\Models\TerminalSyncLog;
use App\Jobs\SyncTerminalConfigJob;
/**
* 终端配置同步服务
*/
class TerminalSyncService
{
/**
* 同步终端配置
*
* @param Terminal $terminal
* @return TerminalSyncLog
*/
public function syncConfiguration(Terminal $terminal): TerminalSyncLog
{
// 创建同步日志记录
$log = TerminalSyncLog::create([
'terminal_id' => $terminal->id,
'status' => 'pending',
'config_snapshot' => $this->getConfigSnapshot($terminal),
]);
// 触发异步同步任务
dispatch(new SyncTerminalConfigJob($terminal, $log));
return $log;
}
/**
* 获取终端配置快照
*
* @param Terminal $terminal
* @return array
*/
public function getConfigSnapshot(Terminal $terminal): array
{
// 加载关联数据
$terminal->load(['knowledgeBases', 'prompt']);
return [
'terminal' => [
'id' => $terminal->id,
'name' => $terminal->name,
'code' => $terminal->code,
'ip_address' => $terminal->ip_address,
'station_id' => $terminal->station_id,
'diagram_url' => $terminal->diagram_url,
'display_config' => $terminal->display_config,
],
'knowledge_bases' => $terminal->knowledgeBases->map(function ($kb) {
return [
'id' => $kb->id,
'name' => $kb->name,
'priority' => $kb->pivot->priority,
];
})->toArray(),
'prompt' => $terminal->prompt ? [
'prompt_template' => $terminal->prompt->prompt_template,
'variables' => $terminal->prompt->variables,
] : null,
];
}
/**
* 更新同步状态
*
* @param TerminalSyncLog $log
* @param string $status
* @param string|null $errorMessage
* @return void
*/
public function updateSyncStatus(TerminalSyncLog $log, string $status, ?string $errorMessage = null): void
{
$data = ['status' => $status];
if ($status === 'synced') {
$data['synced_at'] = now();
}
if ($errorMessage) {
$data['error_message'] = $errorMessage;
}
$log->update($data);
}
/**
* 批量同步终端配置
*
* @param array $terminalIds
* @return array
*/
public function batchSync(array $terminalIds): array
{
$logs = [];
foreach ($terminalIds as $terminalId) {
$terminal = Terminal::find($terminalId);
if ($terminal) {
$logs[] = $this->syncConfiguration($terminal);
}
}
return $logs;
}
}