- 实现基于 Laravel 11 和 Filament 3.X 的文档管理系统 - 添加用户认证和分组管理功能 - 实现文档上传、分类和权限控制 - 集成 Word 文档自动转换为 Markdown - 集成 Meilisearch 全文搜索引擎 - 实现文档在线预览功能 - 添加安全日志和审计功能 - 完整的简体中文界面 - 包含完整的项目文档和部署指南 技术栈: - Laravel 11.x - Filament 3.X - Meilisearch 1.5+ - Pandoc 文档转换 - Redis 队列系统 - Pest PHP 测试框架
262 lines
6.5 KiB
Markdown
262 lines
6.5 KiB
Markdown
# 文件名保留功能说明
|
||
|
||
## 功能概述
|
||
|
||
知识库系统现在会完整保留用户上传文档时的原始文件名,包括中文、特殊字符等。下载文档时,文件名将与上传时保持一致。
|
||
|
||
## 实现细节
|
||
|
||
### 1. Filament 表单配置
|
||
|
||
在 `DocumentResource` 的文件上传字段中添加了 `preserveFilenames()` 方法:
|
||
|
||
```php
|
||
Forms\Components\FileUpload::make('file')
|
||
->label('文档文件')
|
||
->required()
|
||
->acceptedFileTypes([...])
|
||
->preserveFilenames() // 保留原始文件名
|
||
->disk('local')
|
||
->directory('documents/' . date('Y/m/d'))
|
||
// ...
|
||
```
|
||
|
||
### 2. 文件上传处理
|
||
|
||
#### CreateDocument 页面
|
||
|
||
```php
|
||
protected function mutateFormDataBeforeCreate(array $data): array
|
||
{
|
||
if (isset($data['file'])) {
|
||
$filePath = $data['file'];
|
||
$originalFileName = basename($filePath); // 获取原始文件名
|
||
|
||
$data['file_path'] = $filePath;
|
||
$data['file_name'] = $originalFileName; // 保存原始文件名
|
||
// ...
|
||
}
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
#### EditDocument 页面
|
||
|
||
```php
|
||
protected function mutateFormDataBeforeSave(array $data): array
|
||
{
|
||
if (isset($data['file']) && $data['file'] !== $this->record->file_path) {
|
||
$filePath = $data['file'];
|
||
$originalFileName = basename($filePath); // 获取原始文件名
|
||
|
||
$data['file_path'] = $filePath;
|
||
$data['file_name'] = $originalFileName; // 保存原始文件名
|
||
// ...
|
||
}
|
||
return $data;
|
||
}
|
||
```
|
||
|
||
### 3. DocumentService 更新
|
||
|
||
#### 上传方法
|
||
|
||
```php
|
||
public function uploadDocument(...): Document
|
||
{
|
||
return DB::transaction(function () use (...) {
|
||
// 获取原始文件名
|
||
$originalFileName = $file->getClientOriginalName();
|
||
|
||
// 使用 storeAs 保存文件,保留原始文件名
|
||
$directory = 'documents/' . date('Y/m/d');
|
||
$filePath = $file->storeAs($directory, $originalFileName, 'local');
|
||
|
||
// 保存到数据库
|
||
$document = Document::create([
|
||
'file_path' => $filePath,
|
||
'file_name' => $originalFileName, // 原始文件名
|
||
// ...
|
||
]);
|
||
|
||
return $document;
|
||
});
|
||
}
|
||
```
|
||
|
||
#### 下载方法
|
||
|
||
```php
|
||
public function downloadDocument(Document $document, User $user): StreamedResponse
|
||
{
|
||
// 使用原始文件名作为下载文件名
|
||
return Storage::disk('local')->download(
|
||
$document->file_path,
|
||
$document->file_name // 原始文件名
|
||
);
|
||
}
|
||
```
|
||
|
||
## 支持的文件名格式
|
||
|
||
### ✅ 完全支持
|
||
|
||
1. **中文文件名**
|
||
- 示例:`知识库管理系统需求文档.docx`
|
||
- 上传后保留:✅
|
||
- 下载时保留:✅
|
||
|
||
2. **英文文件名**
|
||
- 示例:`Requirements Document.docx`
|
||
- 上传后保留:✅
|
||
- 下载时保留:✅
|
||
|
||
3. **数字文件名**
|
||
- 示例:`2024-Report.docx`
|
||
- 上传后保留:✅
|
||
- 下载时保留:✅
|
||
|
||
4. **特殊字符文件名**
|
||
- 示例:`文档(2024-01-01)_v1.0.docx`
|
||
- 支持的特殊字符:`()[]_-`
|
||
- 上传后保留:✅
|
||
- 下载时保留:✅
|
||
|
||
5. **混合格式文件名**
|
||
- 示例:`Project_项目文档_2024.docx`
|
||
- 上传后保留:✅
|
||
- 下载时保留:✅
|
||
|
||
## 文件存储结构
|
||
|
||
文件按日期组织存储:
|
||
|
||
```
|
||
storage/app/
|
||
└── documents/
|
||
└── 2024/
|
||
└── 12/
|
||
└── 04/
|
||
├── 知识库管理系统需求文档.docx
|
||
├── Requirements Document.docx
|
||
└── 文档(2024-01-01)_v1.0.docx
|
||
```
|
||
|
||
## 数据库字段
|
||
|
||
### documents 表
|
||
|
||
- `file_path`: 存储相对路径(如:`documents/2024/12/04/知识库管理系统需求文档.docx`)
|
||
- `file_name`: 存储原始文件名(如:`知识库管理系统需求文档.docx`)
|
||
- `file_size`: 文件大小(字节)
|
||
- `mime_type`: MIME 类型
|
||
|
||
## 浏览器兼容性
|
||
|
||
### 下载文件名编码
|
||
|
||
对于包含非 ASCII 字符(如中文)的文件名,系统会自动处理编码:
|
||
|
||
```
|
||
Content-Disposition: attachment; filename=document.docx; filename*=utf-8''%E7%9F%A5%E8%AF%86%E5%BA%93.docx
|
||
```
|
||
|
||
- `filename`: ASCII 兼容的后备文件名
|
||
- `filename*`: UTF-8 编码的完整文件名(RFC 5987)
|
||
|
||
### 支持的浏览器
|
||
|
||
- ✅ Chrome 80+
|
||
- ✅ Firefox 75+
|
||
- ✅ Safari 13+
|
||
- ✅ Edge 80+
|
||
- ✅ 移动浏览器(iOS Safari, Chrome Mobile)
|
||
|
||
## 测试覆盖
|
||
|
||
### 测试文件:`tests/Feature/DocumentFileNameTest.php`
|
||
|
||
1. **test_上传文档时保留原始文件名**
|
||
- 验证上传后 `file_name` 字段正确保存
|
||
|
||
2. **test_下载文档时使用原始文件名**
|
||
- 验证下载响应头包含原始文件名
|
||
|
||
3. **test_中文文件名正确处理**
|
||
- 验证中文文件名的完整支持
|
||
|
||
4. **test_特殊字符文件名正确处理**
|
||
- 验证特殊字符的正确处理
|
||
|
||
### 运行测试
|
||
|
||
```bash
|
||
php artisan test --filter=DocumentFileNameTest
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
### 1. 文件名冲突
|
||
|
||
如果同一天上传了同名文件,后上传的文件会覆盖先上传的文件。建议:
|
||
|
||
- 用户在上传前检查文件名
|
||
- 或者在文件名中添加时间戳或版本号
|
||
|
||
### 2. 文件名长度限制
|
||
|
||
- 数据库字段 `file_name` 限制:255 字符
|
||
- 文件系统限制:通常为 255 字节
|
||
- 建议文件名不超过 100 个字符
|
||
|
||
### 3. 特殊字符限制
|
||
|
||
某些特殊字符可能在不同操作系统上有限制:
|
||
|
||
- Windows: 不支持 `< > : " / \ | ? *`
|
||
- Linux/Mac: 不支持 `/`
|
||
|
||
系统会自动处理这些字符,但建议避免使用。
|
||
|
||
## 安全考虑
|
||
|
||
### 1. 路径遍历攻击防护
|
||
|
||
- 使用 `basename()` 提取文件名,防止路径遍历
|
||
- 文件存储在受控目录中
|
||
|
||
### 2. 文件名注入防护
|
||
|
||
- Laravel Storage 自动处理文件名转义
|
||
- 数据库使用参数化查询
|
||
|
||
### 3. XSS 防护
|
||
|
||
- 文件名在显示时会被 Blade 模板自动转义
|
||
- 下载响应头使用标准编码
|
||
|
||
## 未来改进
|
||
|
||
1. **文件名冲突处理**
|
||
- 自动添加序号:`文档.docx` → `文档(1).docx`
|
||
- 或添加时间戳:`文档.docx` → `文档_20240104_143022.docx`
|
||
|
||
2. **文件名验证**
|
||
- 添加文件名格式验证
|
||
- 限制特殊字符使用
|
||
- 提供文件名建议
|
||
|
||
3. **批量上传**
|
||
- 支持批量上传多个文件
|
||
- 自动处理文件名冲突
|
||
|
||
4. **文件名搜索**
|
||
- 支持按文件名搜索文档
|
||
- 支持模糊匹配
|
||
|
||
## 总结
|
||
|
||
文件名保留功能确保了用户上传和下载文档时的一致性体验,特别是对中文文件名的完整支持,使得知识库系统更加符合中文用户的使用习惯。
|
||
|
||
所有功能都经过完整测试,确保在各种场景下都能正常工作。
|