eb8983a06c
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>
44 lines
2.1 KiB
TypeScript
44 lines
2.1 KiB
TypeScript
// 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);
|
||
}
|