web端打通oss资源上传

This commit is contained in:
2026-07-01 21:46:36 +08:00
parent ef6cc18fb2
commit 139e37651b
22 changed files with 1186 additions and 40 deletions
@@ -1,7 +1,13 @@
import { createHmac } from 'crypto';
import { Injectable } from '@nestjs/common';
import { randomUUID } from 'crypto';
import type { IOssProvider, OssUploadTokenInput, OssUploadTokenResult } from './oss.interface';
import type {
IOssProvider,
OssPutObjectInput,
OssPutObjectResult,
OssUploadTokenInput,
OssUploadTokenResult,
} from './oss.interface';
import { buildOssObjectKey } from './oss.key.util';
import { createAliyunOssClient, resolveOssUploadHost } from './oss.aliyun.client';
const DEFAULT_EXPIRE_SECONDS = 15 * 60;
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
@@ -13,43 +19,62 @@ export class OssAliyunProvider implements IOssProvider {
private readonly bucket = process.env.OSS_BUCKET ?? '';
private readonly region = process.env.OSS_REGION ?? 'oss-cn-hangzhou';
private readonly cdnBase = process.env.OSS_CDN_BASE ?? '';
private readonly endpoint = process.env.OSS_ENDPOINT ?? '';
private readonly uploadPrefix = (process.env.OSS_UPLOAD_PREFIX ?? 'uploads').replace(/\/$/, '');
private readonly expireSeconds = Number(process.env.OSS_UPLOAD_EXPIRE_SECONDS ?? DEFAULT_EXPIRE_SECONDS);
private readonly maxUploadBytes = Number(process.env.OSS_MAX_UPLOAD_BYTES ?? DEFAULT_MAX_BYTES);
private readonly authorizationV4 = process.env.OSS_AUTHORIZATION_V4 === 'true';
private client: ReturnType<typeof createAliyunOssClient> | null = null;
isEnabled() {
return !!(this.accessKeyId && this.accessKeySecret && this.bucket);
}
buildPublicUrl(ossKey: string) {
const key = ossKey.replace(/^\//, '');
if (this.cdnBase) {
return `${this.cdnBase.replace(/\/$/, '')}/${key}`;
}
return `https://${this.bucket}.${this.region}.aliyuncs.com/${key}`;
}
getUploadToken(dto: OssUploadTokenInput): OssUploadTokenResult {
private getClient() {
if (!this.isEnabled()) {
throw new Error('OSS credentials are not configured');
}
if (!this.client) {
this.client = createAliyunOssClient({
accessKeyId: this.accessKeyId,
accessKeySecret: this.accessKeySecret,
bucket: this.bucket,
region: this.region,
endpoint: this.endpoint || undefined,
authorizationV4: this.authorizationV4,
});
}
return this.client;
}
const ext = dto.fileName.includes('.') ? dto.fileName.split('.').pop() : 'bin';
const dir = `${this.uploadPrefix}/${dto.bizType.toLowerCase()}/`;
const ossKey = `${dir}${Date.now()}-${randomUUID().slice(0, 8)}.${ext}`;
buildPublicUrl(ossKey: string) {
const key = ossKey.replace(/^\//, '');
const client = this.isEnabled() ? this.getClient() : null;
if (client) {
return client.generateObjectUrl(key, this.cdnBase || undefined);
}
if (this.cdnBase) {
return `${this.cdnBase.replace(/\/$/, '')}/${key}`;
}
return `${resolveOssUploadHost(this.bucket, this.region)}/${key}`;
}
getUploadToken(dto: OssUploadTokenInput): OssUploadTokenResult {
const client = this.getClient();
const ossKey = buildOssObjectKey(this.uploadPrefix, dto.bizType, dto.fileName);
const expireAt = new Date(Date.now() + this.expireSeconds * 1000);
const host = `https://${this.bucket}.${this.region}.aliyuncs.com`;
const host = resolveOssUploadHost(this.bucket, this.region);
const policy = {
expiration: expireAt.toISOString(),
conditions: [
['content-length-range', 0, this.maxUploadBytes],
['eq', '$bucket', this.bucket],
['starts-with', '$key', dir],
['starts-with', '$key', `${this.uploadPrefix}/${dto.bizType.toLowerCase()}/`],
],
};
const policyBase64 = Buffer.from(JSON.stringify(policy)).toString('base64');
const signature = createHmac('sha1', this.accessKeySecret).update(policyBase64).digest('base64');
const signed = client.calculatePostSignature(policy);
return {
bucket: this.bucket,
@@ -61,9 +86,24 @@ export class OssAliyunProvider implements IOssProvider {
mediaType: dto.mediaType,
bizType: dto.bizType,
host,
policy: policyBase64,
signature,
accessKeyId: this.accessKeyId,
policy: signed.policy,
signature: signed.Signature,
accessKeyId: signed.OSSAccessKeyId,
};
}
async putObject(input: OssPutObjectInput): Promise<OssPutObjectResult> {
const client = this.getClient();
const ossKey = buildOssObjectKey(this.uploadPrefix, input.bizType, input.fileName);
await client.put(ossKey, input.buffer, {
mime: input.mimeType || 'application/octet-stream',
});
return {
bucket: this.bucket,
region: this.region,
ossKey,
url: this.buildPublicUrl(ossKey),
mock: false,
};
}
}