2026-08-14 23:21:21 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Service\common\upload;
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 对象键规整
|
|
|
|
|
|
*
|
2026-08-19 08:16:49 +08:00
|
|
|
|
* 上传用 normalizeObjectKey(可补 path_prefix),
|
|
|
|
|
|
* 列举 / 删除用 normalizeListedKey(只压斜杠,用服务端真实 key)。
|
2026-08-14 23:21:21 +08:00
|
|
|
|
* 使用方需要有 $this->config(由 OssRuntimeConfigService 注入,含 path_prefix / domain)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
trait ObjectKeyNormalizeTrait
|
|
|
|
|
|
{
|
|
|
|
|
|
/**
|
2026-08-19 08:16:49 +08:00
|
|
|
|
* 压平重复斜杠,不补 path_prefix
|
|
|
|
|
|
*
|
|
|
|
|
|
* 列举结果必须走这里:OSS 回的 key 就是对象真实路径。
|
|
|
|
|
|
* 若再套一层 path_prefix,根目录的历史文件(如 b_xxx.jpg)会被改写成
|
|
|
|
|
|
* uploads/b_xxx.jpg,素材库入库地址全 404。
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function normalizeListedKey(string $key): string
|
|
|
|
|
|
{
|
|
|
|
|
|
return ltrim((string) preg_replace('#/{2,}#', '/', trim($key)), '/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传 / 删除用:缺 path_prefix 时补上,并压平重复斜杠
|
2026-08-14 23:21:21 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 为什么必须压平:uploads//spa/a.jpg 与 uploads/spa/a.jpg 在 OSS 上是两个对象,
|
|
|
|
|
|
* 但拼出来的访问地址会被 CDN 归一成同一个。素材库按 path 建索引,
|
|
|
|
|
|
* 不统一就会落成两条记录,回收删掉其中一条后另一条变成指向已删对象的幽灵。
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function normalizeObjectKey(string $key): string
|
|
|
|
|
|
{
|
2026-08-19 08:16:49 +08:00
|
|
|
|
$key = $this->normalizeListedKey($key);
|
2026-08-14 23:21:21 +08:00
|
|
|
|
if ($key === '') {
|
|
|
|
|
|
return '';
|
|
|
|
|
|
}
|
|
|
|
|
|
$prefix = trim((string) ($this->config['path_prefix'] ?? ''), '/');
|
|
|
|
|
|
if ($prefix !== '' && $key !== $prefix && !str_starts_with($key, $prefix . '/')) {
|
|
|
|
|
|
$key = $prefix . '/' . $key;
|
|
|
|
|
|
}
|
|
|
|
|
|
return $key;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-08-19 08:16:49 +08:00
|
|
|
|
* 列举前缀:只认调用方传入的值,留空 = 扫整个 Bucket
|
2026-08-14 23:21:21 +08:00
|
|
|
|
*
|
2026-08-19 08:16:49 +08:00
|
|
|
|
* 以前空前缀会偷偷套配置里的 path_prefix。种子数据把七牛写成 uploads,
|
|
|
|
|
|
* 而本站历史文件都在根目录(b_*.jpg),一拉就是空列表。
|
|
|
|
|
|
* 素材库文案也是「留空表示整个 Bucket」,这里必须跟文案一致;
|
|
|
|
|
|
* 要收窄范围请在同步抽屉里显式填前缀。
|
2026-08-14 23:21:21 +08:00
|
|
|
|
*/
|
|
|
|
|
|
protected function scopedListPrefix(string $prefix): string
|
|
|
|
|
|
{
|
2026-08-19 08:16:49 +08:00
|
|
|
|
return $this->normalizeListedKey($prefix);
|
2026-08-14 23:21:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 拼公开访问地址;未配置 domain 时回落到调用方给的默认地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function publicUrlOf(string $key, string $fallback = ''): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$domain = rtrim((string) ($this->config['domain'] ?? ''), '/');
|
|
|
|
|
|
return $domain !== '' ? $domain . '/' . $key : $fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 单页列举条数收口,避免上游把 limit 传成 0 或者十万
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function boundedLimit(int $limit): int
|
|
|
|
|
|
{
|
|
|
|
|
|
return max(1, min($limit, 1000));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|