oss1
This commit is contained in:
@@ -2,3 +2,4 @@ export const SMS_PROVIDER = 'SMS_PROVIDER';
|
||||
export const PAY_PROVIDER = 'PAY_PROVIDER';
|
||||
export const DELIVERY_PROVIDER = 'DELIVERY_PROVIDER';
|
||||
export const WECHAT_PROVIDER = 'WECHAT_PROVIDER';
|
||||
export const OSS_PROVIDER = 'OSS_PROVIDER';
|
||||
|
||||
@@ -7,15 +7,19 @@ import { PayWechatProvider } from './pay/pay.wechat.provider';
|
||||
import { DeliveryMockProvider } from './delivery/delivery.mock.provider';
|
||||
import { WechatApiProvider } from './wechat/wechat.api.provider';
|
||||
import { WechatDisabledProvider } from './wechat/wechat.disabled.provider';
|
||||
import { OssMockProvider } from './oss/oss.mock.provider';
|
||||
import { OssAliyunProvider } from './oss/oss.aliyun.provider';
|
||||
import {
|
||||
SMS_PROVIDER,
|
||||
PAY_PROVIDER,
|
||||
DELIVERY_PROVIDER,
|
||||
WECHAT_PROVIDER,
|
||||
OSS_PROVIDER,
|
||||
} from './integrations.constants';
|
||||
import { DELIVERY_QUEUE } from '../jobs/jobs.constants';
|
||||
import type { IWechatProvider } from './wechat/wechat.interface';
|
||||
import type { IPayProvider } from './pay/pay.interface';
|
||||
import type { IOssProvider } from './oss/oss.interface';
|
||||
|
||||
@Module({
|
||||
imports: [BullModule.registerQueue({ name: DELIVERY_QUEUE })],
|
||||
@@ -42,9 +46,19 @@ import type { IPayProvider } from './pay/pay.interface';
|
||||
inject: [PayMockProvider, PayWechatProvider],
|
||||
},
|
||||
{ provide: DELIVERY_PROVIDER, useClass: DeliveryMockProvider },
|
||||
OssMockProvider,
|
||||
OssAliyunProvider,
|
||||
{
|
||||
provide: OSS_PROVIDER,
|
||||
useFactory: (mock: OssMockProvider, aliyun: OssAliyunProvider): IOssProvider => {
|
||||
const cfg = loadAppConfig();
|
||||
return cfg.ossEnabled && aliyun.isEnabled() ? aliyun : mock;
|
||||
},
|
||||
inject: [OssMockProvider, OssAliyunProvider],
|
||||
},
|
||||
SmsMockProvider,
|
||||
DeliveryMockProvider,
|
||||
],
|
||||
exports: [SMS_PROVIDER, PAY_PROVIDER, DELIVERY_PROVIDER, WECHAT_PROVIDER],
|
||||
exports: [SMS_PROVIDER, PAY_PROVIDER, DELIVERY_PROVIDER, WECHAT_PROVIDER, OSS_PROVIDER],
|
||||
})
|
||||
export class IntegrationsModule {}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { createHmac } from 'crypto';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { IOssProvider, OssUploadTokenInput, OssUploadTokenResult } from './oss.interface';
|
||||
|
||||
const DEFAULT_EXPIRE_SECONDS = 15 * 60;
|
||||
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
||||
|
||||
@Injectable()
|
||||
export class OssAliyunProvider implements IOssProvider {
|
||||
private readonly accessKeyId = process.env.OSS_ACCESS_KEY_ID ?? '';
|
||||
private readonly accessKeySecret = process.env.OSS_ACCESS_KEY_SECRET ?? '';
|
||||
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 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);
|
||||
|
||||
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 {
|
||||
if (!this.isEnabled()) {
|
||||
throw new Error('OSS credentials are not configured');
|
||||
}
|
||||
|
||||
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}`;
|
||||
const expireAt = new Date(Date.now() + this.expireSeconds * 1000);
|
||||
const host = `https://${this.bucket}.${this.region}.aliyuncs.com`;
|
||||
|
||||
const policy = {
|
||||
expiration: expireAt.toISOString(),
|
||||
conditions: [
|
||||
['content-length-range', 0, this.maxUploadBytes],
|
||||
['eq', '$bucket', this.bucket],
|
||||
['starts-with', '$key', dir],
|
||||
],
|
||||
};
|
||||
const policyBase64 = Buffer.from(JSON.stringify(policy)).toString('base64');
|
||||
const signature = createHmac('sha1', this.accessKeySecret).update(policyBase64).digest('base64');
|
||||
|
||||
return {
|
||||
bucket: this.bucket,
|
||||
region: this.region,
|
||||
ossKey,
|
||||
url: this.buildPublicUrl(ossKey),
|
||||
mock: false,
|
||||
expireAt: expireAt.toISOString(),
|
||||
mediaType: dto.mediaType,
|
||||
bizType: dto.bizType,
|
||||
host,
|
||||
policy: policyBase64,
|
||||
signature,
|
||||
accessKeyId: this.accessKeyId,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export interface OssUploadTokenInput {
|
||||
bizType: string;
|
||||
mediaType: string;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface OssUploadTokenResult {
|
||||
bucket: string;
|
||||
region: string;
|
||||
ossKey: string;
|
||||
url: string;
|
||||
mock: boolean;
|
||||
expireAt: string;
|
||||
mediaType: string;
|
||||
bizType: string;
|
||||
/** OSS PostObject 直传 host,mock 时为空 */
|
||||
host?: string;
|
||||
/** Base64 policy,mock 时为空 */
|
||||
policy?: string;
|
||||
/** HMAC-SHA1 签名,mock 时为空 */
|
||||
signature?: string;
|
||||
/** RAM AccessKeyId,mock 时为空 */
|
||||
accessKeyId?: string;
|
||||
}
|
||||
|
||||
export interface IOssProvider {
|
||||
isEnabled(): boolean;
|
||||
getUploadToken(input: OssUploadTokenInput): OssUploadTokenResult;
|
||||
buildPublicUrl(ossKey: string): string;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { IOssProvider, OssUploadTokenInput, OssUploadTokenResult } from './oss.interface';
|
||||
|
||||
@Injectable()
|
||||
export class OssMockProvider implements IOssProvider {
|
||||
private readonly bucket = process.env.OSS_BUCKET || 'mock-dukang';
|
||||
private readonly region = process.env.OSS_REGION || 'oss-cn-hangzhou';
|
||||
private readonly cdnBase = process.env.OSS_CDN_BASE || 'https://mock-cdn.dukang.local';
|
||||
|
||||
isEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
buildPublicUrl(ossKey: string) {
|
||||
return `${this.cdnBase.replace(/\/$/, '')}/${ossKey.replace(/^\//, '')}`;
|
||||
}
|
||||
|
||||
getUploadToken(dto: OssUploadTokenInput): OssUploadTokenResult {
|
||||
const ext = dto.fileName.includes('.') ? dto.fileName.split('.').pop() : 'bin';
|
||||
const key = `uploads/${dto.bizType.toLowerCase()}/${Date.now()}-${randomUUID().slice(0, 8)}.${ext}`;
|
||||
return {
|
||||
bucket: this.bucket,
|
||||
region: this.region,
|
||||
ossKey: key,
|
||||
url: this.buildPublicUrl(key),
|
||||
mock: true,
|
||||
expireAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
|
||||
mediaType: dto.mediaType,
|
||||
bizType: dto.bizType,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user