'', 'path_prefix' => '', ]; /** * 注入运行时配置(链式) */ public function withConfig(array $config): static { $this->config = array_merge($this->config, $config); return $this; } public function uploadImage($filePath, string $key): bool|array { return $this->uploadFile($filePath, $key); } public function uploadVideo($filePath, string $key): bool|array { return $this->uploadFile($filePath, $key); } public function deleteImage($key): bool { return $this->deleteFile($key); } public function deleteVideo($key): bool { return $this->deleteFile($key); } /** * 扫本地磁盘目录 * * 本地盘没有 marker 这种服务端游标,用「已列举条数」当偏移量模拟: * 先把文件名排序固定顺序,再按偏移切页,这样反复调用能稳定推进。 * 代价是同步期间新增文件会让偏移错位,但素材同步是幂等的,下一轮就自愈。 */ public function listObjects(string $prefix = '', string $marker = '', int $limit = 100): array { $listPrefix = rtrim($this->scopedListPrefix($prefix), '/'); $offset = max(0, (int) $marker); $files = Storage::allFiles($listPrefix); sort($files); $page = array_slice($files, $offset, $this->boundedLimit($limit)); $items = []; foreach ($page as $file) { $key = $this->normalizeObjectKey($file); if ($key === '') { continue; } $realPath = Storage::path($file); $items[] = [ 'key' => $key, 'size' => (int) Storage::size($file), // 本地盘没有服务端 ETag,用 md5 顶上,素材库靠它判重与校验 'hash' => is_file($realPath) ? (string) md5_file($realPath) : '', 'last_modified' => (int) Storage::lastModified($file), 'url' => $this->publicUrlOf($key, asset('/storage/' . $key)), ]; } $next = $offset + count($page); $finished = $next >= count($files); return [ 'items' => $items, 'next_marker' => $finished ? '' : (string) $next, 'finished' => $finished, ]; } public function deleteObject(string $key): bool { $key = $this->normalizeObjectKey($key); if ($key === '') { return false; } return $this->deleteFile($key); } /** * 写入本地 storage;兼容 UploadedFile 与路径字符串 */ private function uploadFile(mixed $filePath, string $key): bool|array { $prefix = trim((string) ($this->config['path_prefix'] ?? ''), '/'); if ($prefix !== '' && !str_starts_with($key, $prefix . '/')) { $key = $prefix . '/' . ltrim($key, '/'); } $path = is_object($filePath) && method_exists($filePath, 'getRealPath') ? $filePath->getRealPath() : (string) $filePath; if (!Storage::put($key, file_get_contents($path))) { return false; } $domain = rtrim((string) ($this->config['domain'] ?? ''), '/'); $url = $domain !== '' ? ($domain . '/' . $key) : asset('/storage/' . $key); return [ 'key' => $key, 'url' => $url, ]; } private function deleteFile(string $key): bool { return Storage::delete($key); } }