云储存上传
目录
1. 功能说明
概述
云储存上传模块负责系统中所有文件的上传和管理功能,支持多种存储方式,提供统一的文件上传接口和管理能力。
适用场景
- 用户头像上传
- 商品图片上传
- 视频文件存储
- 文档文件管理
- 富文本编辑器图片上传
功能特点
- 多存储方式支持:本地存储、阿里云OSS、腾讯云COS、七牛云存储
- 文件类型支持:图片、视频、音频、文档等多种类型
- 附件分组管理:支持创建分组、移动附件
- 存储配置灵活:可配置默认存储方式
2. 设计思路
设计理念
采用"统一接口、多存储适配"的设计理念,通过抽象上传接口,实现不同存储方式的无缝切换。
技术选型
| 技术 | 用途 | 选型理由 |
|---|---|---|
| 策略模式 | 多存储适配 | 便于扩展新的存储方式 |
| 文件流处理 | 大文件上传 | 支持大文件分片上传 |
| 数据库存储 | 附件信息管理 | 便于查询和管理 |
架构设计
系统采用分层架构设计:
- 接口层:Controller接收上传请求
- 服务层:Service处理业务逻辑
- 核心层:CoreUpload处理上传逻辑
- 存储层:不同存储方式的具体实现
- 数据层:附件信息持久化
模块划分
upload/
├── controller/ # 控制器层
│ ├── Upload.php # 上传控制器
│ └── Sys.php # 系统配置控制器
├── service/ # 业务逻辑层
│ ├── UploadService.php
│ └── CoreUploadService.php
├── core/ # 核心上传层
│ ├── BaseUpload.php
│ ├── Local.php
│ ├── Aliyun.php
│ ├── Tencent.php
│ ├── Qiniu.php
│ └── UploadLoader.php
└── model/ # 数据模型层
├── Attachment.php
└── AttachmentCategory.php3. 功能流程图
文件上传流程
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 选择文件 │ --> │ 文件验证 │ --> │ 选择存储 │ --> │ 执行上传 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│
v
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 返回URL │ <-- │ 记录附件 │ <-- │ 存储文件 │ <-- │ 生成路径 │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘详细步骤说明
选择文件
- 前端选择要上传的文件
- 支持拖拽上传和点击选择
文件验证
- 验证文件类型是否允许
- 验证文件大小是否超限
- 验证文件是否合法
选择存储方式
- 根据配置选择默认存储
- 支持指定存储方式
执行上传
- 生成存储路径和文件名
- 调用存储驱动上传文件
存储文件
- 本地存储:保存到服务器磁盘
- 云存储:上传到云服务商
记录附件
- 保存附件信息到数据库
- 生成访问URL
返回结果
- 返回文件URL和附件ID
4. 数据表
附件表 (sys_attachment)
| 字段名 | 类型 | 长度 | 是否为空 | 默认值 | 说明 |
|---|---|---|---|---|---|
| att_id | int | 11 | 否 | 自增 | 附件ID,主键 |
| name | varchar | 255 | 否 | - | 附件名称 |
| real_name | varchar | 255 | 是 | NULL | 原始文件名 |
| path | varchar | 500 | 否 | - | 存储路径 |
| url | varchar | 500 | 否 | - | 访问URL |
| att_type | varchar | 50 | 否 | - | 附件类型 |
| att_size | int | 11 | 否 | 0 | 文件大小(字节) |
| storage_type | varchar | 50 | 否 | - | 存储方式 |
| cate_id | int | 11 | 是 | 0 | 分组ID |
| create_time | datetime | - | 否 | CURRENT_TIMESTAMP | 创建时间 |
索引说明
- PRIMARY KEY (
att_id) - KEY
idx_cate_id(cate_id) - KEY
idx_type(att_type)
附件分组表 (sys_attachment_category)
| 字段名 | 类型 | 长度 | 是否为空 | 默认值 | 说明 |
|---|---|---|---|---|---|
| id | int | 11 | 否 | 自增 | 分组ID,主键 |
| name | varchar | 50 | 否 | - | 分组名称 |
| pid | int | 11 | 否 | 0 | 父分组ID |
| sort | int | 11 | 否 | 0 | 排序 |
| create_time | datetime | - | 否 | CURRENT_TIMESTAMP | 创建时间 |
索引说明
- PRIMARY KEY (
id) - KEY
idx_pid(pid)
5. 代码说明
5.1 接口定义
图片上传接口
| 项目 | 说明 |
|---|---|
| 接口地址 | /sys/image |
| 请求方式 | POST |
| Content-Type | multipart/form-data |
请求参数:
| 参数名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| file | file | 是 | 图片文件 |
| cate_id | int | 否 | 分组ID |
响应示例:
json
{
"code": 200,
"message": "上传成功",
"data": {
"url": "https://xxx.com/upload/image/202401/01/xxx.jpg",
"att_id": 10001
}
}5.2 核心代码
上传服务
php
/**
* 图片上传
* @param $file 上传的文件
* @param int $cate_id 分组ID
* @param bool $is_attachment 是否记录附件
* @return array
*/
public function image($file, int $cate_id = 0, $is_attachment = true)
{
$dir = $this->root_path . '/' . 'image' . '/' . date('Ym') . '/' . date('d');
$core_upload_service = new CoreUploadService($is_attachment);
return $core_upload_service->image($file, $dir, $cate_id);
}核心上传处理
php
/**
* 上传后处理
* @param string $file_dir 文件目录
* @param string $type 文件类型
* @param int $cate_id 分组ID
* @return array
*/
public function after(string $file_dir, string $type, int $cate_id = 0)
{
$file_info = $this->upload_driver->getFileInfo();
$dir = $this->root_path . '/' . $file_dir;
// 执行上传
$this->upload_driver->setType($type)->setValidate($this->validate)->upload($dir);
$file_name = $this->upload_driver->getFileName();
$full_path = $this->upload_driver->getFullPath($dir);
$url = $this->upload_driver->getUrl($full_path);
if ($this->is_attachment) {
// 记录附件信息
$data = [
'name' => $file_name,
'dir' => $dir,
'att_type' => $type,
'real_name' => $file_info['name'],
'att_size' => $file_info['size'],
'storage_type' => self::$storage_type,
'path' => $full_path,
'url' => $url,
'cate_id' => $cate_id,
];
$att_id = (new CoreAttachmentService())->add($data);
}
return [
'url' => $url,
'att_id' => $att_id ?? 0
];
}5.3 配置说明
php
// config/storage.php
return [
// 默认存储方式
'default' => 'local',
// 本地存储配置
'local' => [
'driver' => 'local',
'root' => root_path() . 'public/upload',
'url' => '/upload',
],
// 阿里云OSS配置
'aliyun' => [
'driver' => 'aliyun',
'access_key' => env('ALIYUN_ACCESS_KEY'),
'secret_key' => env('ALIYUN_SECRET_KEY'),
'bucket' => env('ALIYUN_BUCKET'),
'endpoint' => env('ALIYUN_ENDPOINT'),
],
// 腾讯云COS配置
'tencent' => [
'driver' => 'tencent',
'secret_id' => env('TENCENT_SECRET_ID'),
'secret_key' => env('TENCENT_SECRET_KEY'),
'bucket' => env('TENCENT_BUCKET'),
'region' => env('TENCENT_REGION'),
],
];6. 常见问题
Q1: 上传失败,提示"文件类型不允许"
问题现象: 上传文件时返回错误,提示文件类型不被允许。
原因分析:
- 文件扩展名不在允许列表中
- 文件MIME类型检测失败
- 文件头信息不完整
解决方案:
- 检查文件扩展名是否在配置允许列表
- 确认文件未损坏且格式正确
- 检查上传配置中的文件类型限制
预防措施:
- 前端提前校验文件类型
- 提供清晰的文件类型提示
- 定期更新允许的文件类型列表
Q2: 大文件上传超时
问题现象: 上传大文件时,请求超时或上传失败。
原因分析:
- PHP上传大小限制
- 请求超时时间设置过短
- 服务器内存不足
解决方案:
- 修改php.ini配置:ini
upload_max_filesize = 100M post_max_size = 100M max_execution_time = 300 - 使用分片上传功能
- 增加服务器内存限制
预防措施:
- 前端显示上传进度
- 大文件自动启用分片上传
- 合理设置文件大小限制
Q3: 云存储配置后无法上传
问题现象: 配置云存储后,上传文件报错或失败。
原因分析:
- 云存储密钥配置错误
- 存储桶权限设置不正确
- 网络连接问题
解决方案:
- 检查AccessKey和SecretKey是否正确
- 确认存储桶读写权限已开启
- 测试与云服务商的网络连通性
- 查看云服务商控制台是否有相关错误日志
预防措施:
- 配置后提供测试连接功能
- 定期检查存储配置有效性
- 设置配置错误告警
Q4: 附件删除后文件仍然存在
问题现象: 删除附件记录后,存储中的文件没有被删除。
原因分析:
- 删除逻辑只删除了数据库记录
- 文件权限不足无法删除
- 文件被其他记录引用
解决方案:
- 检查删除逻辑是否包含文件删除操作
- 确认服务器对存储目录有删除权限
- 实现文件引用计数,无引用时才删除
预防措施:
- 定期清理无效文件
- 实现文件引用计数机制
- 提供手动清理工具
修订记录
| 版本 | 日期 | 修订人 | 修订内容 |
|---|---|---|---|
| v1.0 | 2024-01-01 | - | 初始版本 |
| v2.0 | 2024-04-02 | - | 按照开发手册规范重构文档结构 |
