* 列出当前目录下的文件 * 自动处理分页 * @returns 文件信息数组
()
| 200 | * @returns 文件信息数组 |
| 201 | */ |
| 202 | async list(): Promise<FileInfo[]> { |
| 203 | let prefix = this.basePath === "/" ? "" : this.basePath.substring(1); |
| 204 | // 确保 prefix 以 / 结尾(除了根目录),这样才能正确列出目录下的文件 |
| 205 | if (prefix && !prefix.endsWith("/")) { |
| 206 | prefix += "/"; |
| 207 | } |
| 208 | const files: FileInfo[] = []; |
| 209 | let continuationToken: string | undefined; |
| 210 | |
| 211 | try { |
| 212 | do { |
| 213 | const queryParams: Record<string, string> = { |
| 214 | "list-type": "2", |
| 215 | delimiter: "/", |
| 216 | "max-keys": "1000", |
| 217 | }; |
| 218 | if (prefix) queryParams["prefix"] = prefix; |
| 219 | if (continuationToken) queryParams["continuation-token"] = continuationToken; |
| 220 | |
| 221 | const response = await this.client.request("GET", this.bucket, undefined, { queryParams }); |
| 222 | const xml = await response.text(); |
| 223 | const result = parseListObjectsV2(xml); |
| 224 | |
| 225 | for (const obj of result.contents) { |
| 226 | if (!obj.key) continue; |
| 227 | if (obj.key.endsWith("/")) continue; // 跳过目录占位符 |
| 228 | if (prefix && obj.key === prefix) continue; // 跳过 prefix 本身 |
| 229 | |
| 230 | const relativeKey = prefix ? obj.key.slice(prefix.length) : obj.key; |
| 231 | if (!relativeKey) continue; |
| 232 | |
| 233 | const lastModified = new Date(obj.lastModified).getTime() || Date.now(); |
| 234 | |
| 235 | files.push({ |
| 236 | name: relativeKey, |
| 237 | path: this.basePath, |
| 238 | size: obj.size || 0, |
| 239 | digest: obj.etag?.replace(/"/g, "") || "", |
| 240 | createtime: lastModified, |
| 241 | updatetime: lastModified, |
| 242 | }); |
| 243 | } |
| 244 | |
| 245 | continuationToken = result.nextContinuationToken; |
| 246 | } while (continuationToken); |
| 247 | |
| 248 | if (files.length > 10000) { |
| 249 | console.warn(`Directory listing truncated: >10000 items under ${this.basePath}`); |
| 250 | } |
| 251 | |
| 252 | return files; |
| 253 | } catch (error: any) { |
| 254 | if (error instanceof S3Error && error.code === "AccessDenied") { |
| 255 | throw new Error(`Permission denied. Check your IAM permissions for bucket: ${this.bucket}`); |
| 256 | } |
| 257 | throw error; |
| 258 | } |
| 259 | } |