文件附件与 后端FileSystemHelper 使用指南
文件附件与 后端FileSystemHelper 使用指南
目录
强制约定
业务附件统一走前端直传;元数据落
adc-public;业务只存object_key;回显必须用TgkwAdc\Helper\FileSystemHelper::genFileTempUrl()生成临时访问链接。
- 前端直传:业务附件由前端预签名后直传云存储,后端不经手文件流(服务端生成文件等例外场景除外)。
- 统一落库:附件主数据写入
adc-public的files表(object_key、filename、extension、size、storage、is_upload、is_used等)。 - 业务只存 key:各业务服务只持久化
object_key及业务关联字段,禁止存永久 URL。 - 回显临时链:列表/详情等 API 返回附件时,必须通过
FileSystemHelper::genFileTempUrl($object_key)生成临时访问链接。
违反上述约定的代码不应通过代码审查。
全链路概览
前端选文件
→ POST public/v1/file/upload/pre-sign (拿预签名 + object_key,public 落库 is_upload=0)
→ 按 active_driver 直传云存储
→ GET public/v1/file/upload/callback (回填 size,is_upload=1)
→ 业务接口提交:只传 object_key(及约定展示字段)
→ 业务落库关联 object_key
→ JSON-RPC 标记占用:PublicServiceInterface::handleFileUsed / handleFilesUsed(is_used=1)
→ 详情/列表回显:FileSystemHelper::genFileTempUrl(object_key) → 临时 URL删除业务关联时,对应调用 handleFileUsed($objectKey, 0) / handleFilesUsed(..., 0) 解除占用。
核心原则:云上存文件,public 存附件主数据,业务存引用并 RPC 标记占用,展示时再签临时链。
职责边界
| 角色 | 职责 |
|---|---|
| 前端 | 预签名、直传、callback;业务请求只携带 object_key(及 filename/size 等约定字段) |
| adc-public | 附件主数据落库;预签名 / callback / is_used 管理 |
| 业务服务 | 只存 object_key + 业务关联;RPC 占用 / getFileInfo;回显生成临时链接(不关心存储驱动) |
FileSystemHelper | 按平台配置使用当前驱动;genFileTempUrl、例外场景的服务端 upload |
前端直传对接
1. 申请预签名
POST /public/v1/file/upload/pre-sign
请求体示例:
{
"filename": "合同扫描件.pdf"
}响应关键字段:
| 字段 | 说明 |
|---|---|
active_driver | 当前存储驱动(如 oss / cos / obs / s3 / minio 等) |
sign | 按驱动返回的直传表单字段,其中 key 即为 object_key |
callback_url | 直传完成后的回调路径(如 public/v1/file/upload/callback) |
2. 直传云存储
按 active_driver 将文件以表单 POST 到云厂商返回的 url,表单字段以 sign[active_driver] 为准(含 policy、signature、key 等)。
直传成功后,业务侧只保留返回的 key(object_key),不要保存云返回的永久访问地址。
3. 上传回调
GET /public/v1/file/upload/callback
将 object_key 传回 public,服务端从云上读取文件大小并更新:
sizeis_upload = 1
4. 提交业务
业务创建/编辑接口只提交 object_key(可附带 filename、size 等展示字段,以各业务 API 约定为准)。不要把临时 URL 或永久 URL 写入业务库。
public 附件落库
统一附件记录在 adc-public 的 files 表,典型字段:
| 字段 | 含义 |
|---|---|
object_key | 云存储对象键(全局引用主键) |
filename | 原始文件名 |
extension | 扩展名 |
size | 字节大小(callback 后回填) |
storage | 存储驱动名 |
is_upload | 是否已上传至云(预签名后为 0,callback 后为 1) |
is_used | 是否已被业务占用 |
对应服务:public 的文件服务(预签名 / 回调 / 占用标记 / getFileInfo / getFilesInfo)。
标记占用(JSON-RPC)
use TgkwAdc\JsonRpc\Public\PublicServiceInterface;
// 单文件
$this->publicService->handleFileUsed($objectKey, 1);
// 批量
$this->publicService->handleFilesUsed($objectKeys, 1);
// 解除占用(删除关联时)
$this->publicService->handleFileUsed($objectKey, 0);未占用(
is_used=0)的文件可能被清理任务回收,业务绑定成功后应及时标记占用。
获取文件原始数据(JSON-RPC getFileInfo / getFilesInfo)
业务侧需要权威文件元数据(文件名、扩展名、大小、是否已上传、是否占用等)时,通过 RPC 按 object_key 查询,不要自行拼装或猜测。
单条:
use TgkwAdc\JsonRpc\Public\PublicServiceInterface;
/** @var object|array $fileInfo */
$fileInfo = $this->publicService->getFileInfo($objectKey);
// 常见字段:id、object_key、filename、extension、size、
// is_upload、is_used、storage、video_duration、updated_at 等批量(推荐列表/多附件场景):
/** @var array<string, object|array> $fileMap */
$fileMap = $this->publicService->getFilesInfo($objectKeys);
// 以 object_key 为键;任一 key 不存在则抛「文件不存在」
$fileInfo = $fileMap[$objectKey] ?? null;适用场景示例:
| 场景 | 说明 |
|---|---|
| 业务落库补全元数据 | 前端只传 object_key,filename/size/extension 以 RPC 为准写入业务关联表 |
| 校验文件是否存在 / 已上传 | 绑定前确认 is_upload=1,避免引用未完成直传的记录 |
| 详情 / 列表多附件 | 优先 getFilesInfo,避免对每个 key 循环调用 getFileInfo |
接口定义见:TgkwAdc\JsonRpc\Public\PublicServiceInterface。
getFileInfo/getFilesInfo返回的是 public 落库的原始元数据,不包含可长期使用的访问 URL。对外展示链接仍须用FileSystemHelper::genFileTempUrl($object_key)生成临时链。
业务服务接入
落库
业务表只存引用,例如:
| 业务字段示例 | 说明 |
|---|---|
object_key / file_key | 对应 public files.object_key |
filename / file_name | 可选,便于列表展示 |
size / file_size | 可选 |
不要新增「永久 url」「cdn_url」一类字段作为权威数据源。
回显
在 Resource 或 Support 层统一转换:
use TgkwAdc\Helper\FileSystemHelper;
$fs = new FileSystemHelper();
$url = $objectKey !== '' ? $fs->genFileTempUrl($objectKey) : '';临时链接有过期时间(默认约 1 天),每次接口响应时重新生成即可。
FileSystemHelper
类:TgkwAdc\Helper\FileSystemHelper(包:tgkw-adc-helper)
| 方法 | 用途 |
|---|---|
genFileTempUrl($object_key, $expiresAt = '+1 days') | 回显必用:生成临时访问链接(返回 https) |
genFileName($extension) | 服务端生成 object_key(非直传场景) |
upload($object_key, $path) | 服务端上传本地文件(导入错误文件等例外) |
getAdapter() / getAdapterName() | 当前存储适配器实例 / 驱动名(平台内部使用) |
存储驱动由平台统一配置,业务系统无需、也禁止自选驱动。
正确用法:始终使用默认构造,由工厂按配置中心下发的当前驱动工作:
$fs = new FileSystemHelper();
$url = $fs->genFileTempUrl($objectKey);禁止:new FileSystemHelper('oss')、setAdapter('cos') 等在业务代码中硬编码或切换驱动。驱动变更由平台总后台调整,经配置中心发布后各服务自动生效。
过期时间
第二个参数为相对时间字符串,例如:
$fs->genFileTempUrl($objectKey); // 默认 +1 days
$fs->genFileTempUrl($objectKey, '+2 hours');
$fs->genFileTempUrl($objectKey, '+1 day');回显通用模式
建议在 Resource / Support 中封装统一结构,至少包含:
| 字段 | 说明 |
|---|---|
object_key | 对象键 |
filename | 文件名 |
extension | 扩展名(可选) |
size | 大小 |
url / download_url | 临时链接(由 Helper 生成) |
通用示例:
use TgkwAdc\Helper\FileSystemHelper;
function formatAttachment(object $row): array
{
$fs = new FileSystemHelper();
$objectKey = trim((string) ($row->object_key ?? $row->file_key ?? ''));
return [
'id' => (string) ($row->id ?? ''),
'object_key' => $objectKey,
'filename' => (string) ($row->filename ?? $row->file_name ?? ''),
'size' => (int) ($row->size ?? $row->file_size ?? 0),
'download_url' => $objectKey !== '' ? $fs->genFileTempUrl($objectKey) : null,
];
}同一请求内可复用同一个 FileSystemHelper 实例,避免重复初始化。
样例代码
1. Resource / Support 中格式化附件回显
use TgkwAdc\Helper\FileSystemHelper;
$fs = new FileSystemHelper();
$objectKey = trim((string) ($attachment->object_key ?? $attachment->file_key ?? ''));
return [
'object_key' => $objectKey,
'filename' => (string) ($attachment->filename ?? $attachment->name ?? ''),
'size' => (int) ($attachment->size ?? $attachment->file_size ?? 0),
'download_url' => $objectKey !== '' ? $fs->genFileTempUrl($objectKey) : null,
];2. 业务绑定:补全元数据 + 标记占用
use TgkwAdc\JsonRpc\Public\PublicServiceInterface;
$fileInfo = $this->publicService->getFileInfo($objectKey);
// 多附件时优先:$fileMap = $this->publicService->getFilesInfo($objectKeys);
// 业务关联表只存引用与展示所需元数据
$record->object_key = $objectKey;
$record->filename = $fileInfo->filename ?? ($fileInfo['filename'] ?? '');
$record->size = (int) ($fileInfo->size ?? ($fileInfo['size'] ?? 0));
$record->save();
$this->publicService->handleFileUsed($objectKey, 1);3. 解除占用
$this->publicService->handleFileUsed($objectKey, 0);
// 或批量:$this->publicService->handleFilesUsed($objectKeys, 0);禁止清单
| 禁止行为 | 正确做法 |
|---|---|
| 业务库存永久 URL / CDN 地址 | 只存 object_key,回显时签临时链 |
| 回显时手写拼接桶域名 + key | 使用 genFileTempUrl |
| 统一附件场景自建附件主表 | 主数据落 public files |
绑定业务后不标记 is_used | RPC handleFileUsed(..., 1) |
| 需要元数据却不查 public | RPC getFileInfo / getFilesInfo |
| 业务代码自选 / 硬编码存储驱动 | 使用 new FileSystemHelper(),由配置中心统一发布驱动 |
| 把过期临时 URL 再写回数据库 | 每次响应重新生成 |
| 普通业务接口接收 multipart 再转存云 | 走预签名直传(例外:导入结果文件等服务端生成场景) |
FAQ
为什么不能存永久 URL?
对象存储通常为私有读;永久链接不安全且无法统一过期与权限。临时链由当前存储驱动签发,可控制有效期。
临时链接过期了怎么办?
客户端再次请求业务详情/列表接口即可;服务端每次用 object_key 重新 genFileTempUrl。不要让前端长期缓存临时 URL 当永久地址用。
业务要不要关心存储驱动(oss/cos/…)?
不用。 当前驱动由平台总后台统一配置,经配置中心发布。业务只使用默认的 new FileSystemHelper(),禁止在业务代码中指定或切换驱动。
什么时候允许服务端 upload?
仅限服务端生成文件的场景,例如导入失败结果 Excel、系统导出文件等。用户主动上传的业务附件一律走前端直传。
业务表已经有 filename/size,还要不要查 public?
列表/详情展示可用业务冗余字段;权威元数据与占用状态以 public files 为准。需要补全、校验或回源时,调用 getFileInfo / getFilesInfo。
POST public/v1/file/upload 还能用吗?
该接口为服务端接收上传的调试/兼容路径,业务主流程应使用预签名直传,不要依赖网关转发大文件。