Files
photo-live/packages/storage/src/index.ts
T
浅念 eb8983a06c feat(storage,g1): real-OSS support and G1 smoke — gate CLOSED
G1 (skipped at P1 for lack of credentials) executed 2026-09-21 against the
user-provided bucket live-album @ oss-cn-beijing:

Raw-SDK smoke (scripts/g1-oss-smoke.mjs, creds via runtime env only):
- HeadBucket / presign PUT direct upload / head byte+type verify /
  presign GET round-trip / copy staging→originals / idempotent replay /
  DeleteObject cleanup — all pass.
- S3-compat lifecycle query unsupported → console manual check remains the
  only open G1 sub-item (need: confirm no lifecycle rule hits photo-live/).

Adapter-level (production path, createStorageFromEnv → real OSS):
- presignPut+PUT 200, head verify, presignGet round-trip equal,
  copyIfAbsent idempotent (created:true→false, same etag),
  forbid-overwrite guard throws UPLOAD_VERIFY_FAILED — all pass.

Findings locked in:
- Addressing: OSS S3-compat requires virtual-host style with BASE endpoint
  (path-style → 403 signature mismatch). StoragePort adapter gains optional
  MinioStorageConfig.forcePathStyle + S3_FORCE_PATH_STYLE env (default true
  keeps local MinIO behavior); required-field validation fixed to string
  fields only (boolean false ≠ missing).
- .env.production.example-cn S3 section updated to the confirmed shape
  (beijing endpoint/region, virtual-host, bucket live-album).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-21 10:16:33 +08:00

44 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// packages/storage/src/index.ts —— 存储包统一出口:端口类型 + MinIO 适配器 + 错误 +
// env 工厂。api(Task 18 presign/verify)与 worker(Task 20 variants put/get)经
// `createStorageFromEnv()` 构造实例,测试/本地指向 MinIO,生产指向 OSS。
// commonjs 包(消费方 api/worker 均为 CJS);@photo/contracts 为 ESM dist,运行时经
// Node require(esm) 消费(Node >= 22.12,见根 engines)。
import { MinioStorageAdapter, type MinioStorageConfig } from './minio.adapter';
export { MinioStorageAdapter } from './minio.adapter';
export type { MinioStorageConfig } from './minio.adapter';
export { StorageError, ErrorCodes } from './errors';
export type { StoragePort, UploadCredential } from './port';
export {
OBJECT_KEY_ROOT,
variantsObjectKey,
watermarkVariantObjectKey,
} from './keys';
export type { PhotoVariantName } from './keys';
/**
* 从 S3_* 环境变量构造 MinIO/S3 兼容适配器(仓库根 .env 经 dotenv-cli 或
* test/load-env.ts 注入)。任一变量缺失即抛错,禁止静默回退默认值。
*/
export function createStorageFromEnv(): MinioStorageAdapter {
const config = {
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
accessKey: process.env.S3_ACCESS_KEY,
secretKey: process.env.S3_SECRET_KEY,
bucket: process.env.S3_BUCKET,
// S3_FORCE_PATH_STYLE 可选:缺省 true(MinIO path-style);阿里云 OSS 等
// 云形态设 false(virtual-host + 基础 endpoint)——G1 实测定案。
forcePathStyle: process.env.S3_FORCE_PATH_STYLE
? process.env.S3_FORCE_PATH_STYLE === 'true'
: true,
};
// 只校验必填字符串字段(forcePathStyle 是布尔,false 不代表缺失)。
const required = ['endpoint', 'region', 'accessKey', 'secretKey', 'bucket'] as const;
const missing = required.filter((key) => !config[key]).map((key) => `S3_${key.toUpperCase()}`);
if (missing.length > 0) {
throw new Error(`createStorageFromEnv: 缺少环境变量 ${missing.join(', ')}(S3_* 配置见仓库根 .env)`);
}
return new MinioStorageAdapter(config as MinioStorageConfig);
}