- requirements.md: 需求文档 - design.md: 设计文档 - tasks.md: 任务列表 - validation-rules-summary.md: 验证规则总结 阶段二(系统设置与操作日志功能)已完成 ✓
18 KiB
18 KiB
管理后台功能增强 - 设计文档
架构设计
整体架构
┌─────────────────────────────────────────────────────────┐
│ Filament Admin Panel │
├─────────────────────────────────────────────────────────┤
│ 系统设置页面 │ 操作日志页面 │ 大屏配置 │ SOP模板 │
├─────────────────────────────────────────────────────────┤
│ Filament Resources & Pages │
├─────────────────────────────────────────────────────────┤
│ Laravel Models │
├─────────────────────────────────────────────────────────┤
│ MySQL Database │
└─────────────────────────────────────────────────────────┘
数据库设计
1. 系统设置表 (system_settings)
CREATE TABLE system_settings (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`key` VARCHAR(255) NOT NULL UNIQUE COMMENT '配置键',
`value` JSON NOT NULL COMMENT '配置值',
`group` VARCHAR(100) NOT NULL COMMENT '配置分组',
description TEXT COMMENT '配置说明',
is_public BOOLEAN DEFAULT FALSE COMMENT '是否公开',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
INDEX idx_group (`group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2. 操作日志表 (activity_log)
使用 spatie/laravel-activitylog 包的标准表结构
3. 大屏终端表 (terminals)
CREATE TABLE terminals (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL COMMENT '终端名称',
code VARCHAR(100) NOT NULL UNIQUE COMMENT '终端编码',
ip_address VARCHAR(45) COMMENT 'IP地址',
station_id BIGINT UNSIGNED COMMENT '线站ID',
diagram_url VARCHAR(500) COMMENT '组态图URL',
display_config JSON COMMENT '显示配置',
is_online BOOLEAN DEFAULT FALSE COMMENT '在线状态',
last_online_at TIMESTAMP NULL COMMENT '最后在线时间',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL,
INDEX idx_station (station_id),
INDEX idx_online (is_online)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
4. 终端知识库关联表 (terminal_knowledge_bases)
CREATE TABLE terminal_knowledge_bases (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
terminal_id BIGINT UNSIGNED NOT NULL COMMENT '终端ID',
knowledge_base_id BIGINT UNSIGNED NOT NULL COMMENT '知识库ID',
priority INTEGER DEFAULT 0 COMMENT '优先级',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (terminal_id) REFERENCES terminals(id) ON DELETE CASCADE,
UNIQUE KEY uk_terminal_kb (terminal_id, knowledge_base_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
5. 终端提示词表 (terminal_prompts)
CREATE TABLE terminal_prompts (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
terminal_id BIGINT UNSIGNED NOT NULL COMMENT '终端ID',
prompt_template TEXT NOT NULL COMMENT '提示词模板',
variables JSON COMMENT '变量配置',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (terminal_id) REFERENCES terminals(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
6. 终端同步日志表 (terminal_sync_logs)
CREATE TABLE terminal_sync_logs (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
terminal_id BIGINT UNSIGNED NOT NULL COMMENT '终端ID',
status ENUM('pending', 'syncing', 'synced', 'failed') DEFAULT 'pending' COMMENT '同步状态',
config_snapshot JSON COMMENT '配置快照',
synced_at TIMESTAMP NULL COMMENT '同步时间',
error_message TEXT COMMENT '错误信息',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (terminal_id) REFERENCES terminals(id) ON DELETE CASCADE,
INDEX idx_status (status),
INDEX idx_synced_at (synced_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
7. SOP模板表 (sop_templates)
CREATE TABLE sop_templates (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL COMMENT '模板名称',
description TEXT COMMENT '模板描述',
category VARCHAR(100) COMMENT '分类',
tags JSON COMMENT '标签',
version VARCHAR(50) DEFAULT '1.0.0' COMMENT '版本号',
status ENUM('draft', 'published', 'archived') DEFAULT 'draft' COMMENT '状态',
applicable_departments JSON COMMENT '适用部门',
applicable_positions JSON COMMENT '适用岗位',
published_at TIMESTAMP NULL COMMENT '发布时间',
created_by BIGINT UNSIGNED COMMENT '创建人',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
deleted_at TIMESTAMP NULL,
INDEX idx_status (status),
INDEX idx_category (category)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
8. SOP步骤表 (sop_steps)
CREATE TABLE sop_steps (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sop_template_id BIGINT UNSIGNED NOT NULL COMMENT '模板ID',
step_number INTEGER NOT NULL COMMENT '步骤序号',
title VARCHAR(255) NOT NULL COMMENT '步骤标题',
content TEXT COMMENT '步骤内容',
sort_order INTEGER DEFAULT 0 COMMENT '排序',
is_required BOOLEAN DEFAULT TRUE COMMENT '是否必需',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (sop_template_id) REFERENCES sop_templates(id) ON DELETE CASCADE,
INDEX idx_template_sort (sop_template_id, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
9. SOP交互任务表 (sop_interactive_tasks)
CREATE TABLE sop_interactive_tasks (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sop_step_id BIGINT UNSIGNED NOT NULL COMMENT '步骤ID',
task_type ENUM('confirm', 'input', 'select', 'photo', 'scan') NOT NULL COMMENT '任务类型',
task_config JSON COMMENT '任务配置',
validation_rules JSON COMMENT '验证规则',
timeout_seconds INTEGER COMMENT '超时时间',
is_required BOOLEAN DEFAULT TRUE COMMENT '是否必需',
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (sop_step_id) REFERENCES sop_steps(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
10. SOP模板版本表 (sop_template_versions)
CREATE TABLE sop_template_versions (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
sop_template_id BIGINT UNSIGNED NOT NULL COMMENT '模板ID',
version VARCHAR(50) NOT NULL COMMENT '版本号',
change_log TEXT COMMENT '变更说明',
content_snapshot JSON COMMENT '内容快照',
created_by BIGINT UNSIGNED COMMENT '创建人',
created_at TIMESTAMP NULL,
FOREIGN KEY (sop_template_id) REFERENCES sop_templates(id) ON DELETE CASCADE,
INDEX idx_template_version (sop_template_id, version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
模型设计
1. SystemSetting 模型
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SystemSetting extends Model
{
protected $fillable = [
'key', 'value', 'group', 'description', 'is_public'
];
protected $casts = [
'value' => 'array',
'is_public' => 'boolean',
];
// 获取配置值
public static function get(string $key, $default = null)
{
$setting = static::where('key', $key)->first();
return $setting ? $setting->value : $default;
}
// 设置配置值
public static function set(string $key, $value, string $group = 'general')
{
return static::updateOrCreate(
['key' => $key],
['value' => $value, 'group' => $group]
);
}
}
2. Terminal 模型
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class Terminal extends Model
{
use SoftDeletes, LogsActivity;
protected $fillable = [
'name', 'code', 'ip_address', 'station_id',
'diagram_url', 'display_config', 'is_online', 'last_online_at'
];
protected $casts = [
'display_config' => 'array',
'is_online' => 'boolean',
'last_online_at' => 'datetime',
];
public function knowledgeBases()
{
return $this->belongsToMany(KnowledgeBase::class, 'terminal_knowledge_bases')
->withPivot('priority')
->orderBy('priority');
}
public function prompt()
{
return $this->hasOne(TerminalPrompt::class);
}
public function syncLogs()
{
return $this->hasMany(TerminalSyncLog::class);
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'code', 'station_id', 'diagram_url', 'display_config'])
->logOnlyDirty();
}
}
3. SopTemplate 模型
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Activitylog\LogOptions;
class SopTemplate extends Model
{
use SoftDeletes, LogsActivity;
protected $fillable = [
'name', 'description', 'category', 'tags', 'version',
'status', 'applicable_departments', 'applicable_positions',
'published_at', 'created_by'
];
protected $casts = [
'tags' => 'array',
'applicable_departments' => 'array',
'applicable_positions' => 'array',
'published_at' => 'datetime',
];
public function steps()
{
return $this->hasMany(SopStep::class)->orderBy('sort_order');
}
public function versions()
{
return $this->hasMany(SopTemplateVersion::class);
}
public function creator()
{
return $this->belongsTo(User::class, 'created_by');
}
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->logOnly(['name', 'description', 'category', 'status', 'version'])
->logOnlyDirty();
}
}
Filament资源设计
1. SystemSettingResource
- 使用Tabs组件按group分组显示配置
- 使用KeyValue字段编辑JSON配置
- 敏感配置使用Password字段
2. ActivityLogResource
- 只读资源,不允许创建和编辑
- 使用Tables\Filters进行筛选
- 使用Actions\ExportAction导出数据
- 自定义ViewAction显示变更对比
3. TerminalResource
- 使用Badge显示在线状态
- 使用Select2组件选择知识库(多选+搜索)
- 使用MonacoEditor编辑提示词
- 自定义Action触发配置下发
4. SopTemplateResource
- 使用Repeater组件管理步骤
- 使用RichEditor编辑步骤内容
- 使用Builder组件配置交互任务
- 自定义PreviewAction预览模板
API设计
终端配置同步API
POST /api/terminals/{terminal}/sync
Response: {
"success": true,
"sync_log_id": 123,
"message": "配置同步已启动"
}
SOP模板导出API
GET /api/sop-templates/{template}/export?format=json|pdf
Response: File Download
前端组件设计
1. 日志对比组件
// app/Filament/Components/LogDiffViewer.php
class LogDiffViewer extends Component
{
public array $oldData;
public array $newData;
public function render()
{
return view('filament.components.log-diff-viewer');
}
}
2. 终端状态指示器
// app/Filament/Components/TerminalStatusBadge.php
class TerminalStatusBadge extends Component
{
public bool $isOnline;
public ?Carbon $lastOnlineAt;
public function render()
{
return view('filament.components.terminal-status-badge');
}
}
3. SOP步骤编辑器
// app/Filament/Components/SopStepEditor.php
class SopStepEditor extends Component
{
public array $steps = [];
public function addStep()
{
$this->steps[] = [
'title' => '',
'content' => '',
'sort_order' => count($this->steps) + 1,
];
}
public function removeStep($index)
{
unset($this->steps[$index]);
$this->steps = array_values($this->steps);
}
public function reorderSteps($orderedIds)
{
// 重新排序逻辑
}
}
服务层设计
1. SystemSettingService
namespace App\Services;
class SystemSettingService
{
public function getGroupedSettings(): array
{
return SystemSetting::all()
->groupBy('group')
->toArray();
}
public function updateSettings(array $settings): void
{
foreach ($settings as $key => $value) {
SystemSetting::set($key, $value);
}
}
}
2. TerminalSyncService
namespace App\Services;
class TerminalSyncService
{
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;
}
private function getConfigSnapshot(Terminal $terminal): array
{
return [
'terminal' => $terminal->toArray(),
'knowledge_bases' => $terminal->knowledgeBases->toArray(),
'prompt' => $terminal->prompt?->toArray(),
];
}
}
3. SopTemplateService
namespace App\Services;
class SopTemplateService
{
public function publish(SopTemplate $template): void
{
// 创建版本快照
$this->createVersion($template);
// 更新状态
$template->update([
'status' => 'published',
'published_at' => now(),
]);
}
public function createVersion(SopTemplate $template): SopTemplateVersion
{
return SopTemplateVersion::create([
'sop_template_id' => $template->id,
'version' => $template->version,
'content_snapshot' => [
'template' => $template->toArray(),
'steps' => $template->steps->toArray(),
],
'created_by' => auth()->id(),
]);
}
public function export(SopTemplate $template, string $format): string
{
return match($format) {
'json' => $this->exportToJson($template),
'pdf' => $this->exportToPdf($template),
default => throw new \InvalidArgumentException("Unsupported format: $format"),
};
}
}
任务队列设计
1. SyncTerminalConfigJob
namespace App\Jobs;
class SyncTerminalConfigJob implements ShouldQueue
{
public function __construct(
public Terminal $terminal,
public TerminalSyncLog $log
) {}
public function handle(): void
{
try {
$this->log->update(['status' => 'syncing']);
// 调用终端API同步配置
$response = Http::post($this->terminal->sync_url, [
'config' => $this->log->config_snapshot,
]);
if ($response->successful()) {
$this->log->update([
'status' => 'synced',
'synced_at' => now(),
]);
} else {
throw new \Exception($response->body());
}
} catch (\Exception $e) {
$this->log->update([
'status' => 'failed',
'error_message' => $e->getMessage(),
]);
}
}
}
权限设计
策略定义
// app/Policies/SystemSettingPolicy.php
class SystemSettingPolicy
{
public function viewAny(User $user): bool
{
return $user->hasRole('admin');
}
public function update(User $user, SystemSetting $setting): bool
{
return $user->hasRole('admin');
}
}
// app/Policies/TerminalPolicy.php
class TerminalPolicy
{
public function viewAny(User $user): bool
{
return $user->hasAnyRole(['admin', 'terminal_manager']);
}
public function sync(User $user, Terminal $terminal): bool
{
return $user->hasRole('admin');
}
}
// app/Policies/SopTemplatePolicy.php
class SopTemplatePolicy
{
public function publish(User $user, SopTemplate $template): bool
{
return $user->hasAnyRole(['admin', 'content_manager']);
}
}
测试策略
单元测试
- SystemSetting模型的get/set方法
- Terminal模型的关联关系
- SopTemplate的版本管理逻辑
- 各Service类的核心方法
功能测试
- Filament资源的CRUD操作
- 日志筛选和导出功能
- 终端配置同步流程
- SOP模板发布流程
集成测试
- 操作日志自动记录
- 终端配置同步任务
- SOP模板导入导出
性能优化
数据库优化
- 为常用查询字段添加索引
- 使用Eager Loading避免N+1问题
- 大表使用分区(如activity_log)
缓存策略
- 系统设置使用缓存(Cache::remember)
- 终端在线状态使用Redis缓存
- SOP模板列表使用查询缓存
前端优化
- 使用Lazy Loading加载大型列表
- Monaco Editor按需加载
- 图片使用CDN加速
安全考虑
数据安全
- 敏感配置(API密钥)使用加密存储
- 操作日志不可删除
- SOP模板版本不可修改
访问控制
- 基于角色的权限控制
- 敏感操作需要二次确认
- API接口使用认证和授权
输入验证
- 所有表单输入进行验证
- 富文本内容进行XSS过滤
- 文件上传进行类型和大小限制