- requirements.md: 需求文档 - design.md: 设计文档 - tasks.md: 任务列表 - validation-rules-summary.md: 验证规则总结 阶段二(系统设置与操作日志功能)已完成 ✓
647 lines
18 KiB
Markdown
647 lines
18 KiB
Markdown
# 管理后台功能增强 - 设计文档
|
||
|
||
## 架构设计
|
||
|
||
### 整体架构
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Filament Admin Panel │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ 系统设置页面 │ 操作日志页面 │ 大屏配置 │ SOP模板 │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ Filament Resources & Pages │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ Laravel Models │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ MySQL Database │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## 数据库设计
|
||
|
||
### 1. 系统设置表 (system_settings)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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)
|
||
```sql
|
||
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 模型
|
||
```php
|
||
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 模型
|
||
```php
|
||
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 模型
|
||
```php
|
||
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
|
||
```php
|
||
POST /api/terminals/{terminal}/sync
|
||
Response: {
|
||
"success": true,
|
||
"sync_log_id": 123,
|
||
"message": "配置同步已启动"
|
||
}
|
||
```
|
||
|
||
### SOP模板导出API
|
||
```php
|
||
GET /api/sop-templates/{template}/export?format=json|pdf
|
||
Response: File Download
|
||
```
|
||
|
||
## 前端组件设计
|
||
|
||
### 1. 日志对比组件
|
||
```php
|
||
// 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. 终端状态指示器
|
||
```php
|
||
// 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步骤编辑器
|
||
```php
|
||
// 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
|
||
```php
|
||
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
|
||
```php
|
||
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
|
||
```php
|
||
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
|
||
```php
|
||
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(),
|
||
]);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 权限设计
|
||
|
||
### 策略定义
|
||
```php
|
||
// 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过滤
|
||
- 文件上传进行类型和大小限制
|