Commit 6213f000 by roy Committed by GitHub

chore: add extra sub path for custom S3 (#6339)

* fix: storage sdk

* chore: add extra sub path for custom S3
parent cdb896eb
...@@ -20,9 +20,10 @@ export class S3PrivateBucket extends S3BaseBucket { ...@@ -20,9 +20,10 @@ export class S3PrivateBucket extends S3BaseBucket {
region, region,
vendor, vendor,
credentials, credentials,
forcePathStyle: true,
endpoint: options.endpoint!, endpoint: options.endpoint!,
maxRetries: options.maxRetries! maxRetries: options.maxRetries!,
forcePathStyle: options.forcePathStyle,
publicAccessExtraSubPath: options.publicAccessExtraSubPath
} as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>; } as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>;
return { return {
config, config,
...@@ -38,7 +39,8 @@ export class S3PrivateBucket extends S3BaseBucket { ...@@ -38,7 +39,8 @@ export class S3PrivateBucket extends S3BaseBucket {
credentials, credentials,
endpoint: options.endpoint!, endpoint: options.endpoint!,
maxRetries: options.maxRetries!, maxRetries: options.maxRetries!,
forcePathStyle: options.forcePathStyle forcePathStyle: options.forcePathStyle,
publicAccessExtraSubPath: options.publicAccessExtraSubPath
} as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>; } as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>;
return { return {
config, config,
......
...@@ -21,9 +21,10 @@ export class S3PublicBucket extends S3BaseBucket { ...@@ -21,9 +21,10 @@ export class S3PublicBucket extends S3BaseBucket {
region, region,
vendor, vendor,
credentials, credentials,
forcePathStyle: true,
endpoint: options.endpoint!, endpoint: options.endpoint!,
maxRetries: options.maxRetries! maxRetries: options.maxRetries!,
forcePathStyle: options.forcePathStyle,
publicAccessExtraSubPath: options.publicAccessExtraSubPath
} as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>; } as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>;
return { return {
config, config,
...@@ -39,7 +40,8 @@ export class S3PublicBucket extends S3BaseBucket { ...@@ -39,7 +40,8 @@ export class S3PublicBucket extends S3BaseBucket {
credentials, credentials,
endpoint: options.endpoint!, endpoint: options.endpoint!,
maxRetries: options.maxRetries!, maxRetries: options.maxRetries!,
forcePathStyle: options.forcePathStyle forcePathStyle: options.forcePathStyle,
publicAccessExtraSubPath: options.publicAccessExtraSubPath
} as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>; } as Omit<IAwsS3CompatibleStorageOptions, 'bucket'>;
return { return {
config, config,
......
...@@ -47,7 +47,7 @@ export function createDefaultStorageOptions() { ...@@ -47,7 +47,7 @@ export function createDefaultStorageOptions() {
case 'minio': { case 'minio': {
return { return {
vendor: 'minio', vendor: 'minio',
forcePathStyle: true, forcePathStyle: process.env.STORAGE_S3_FORCE_PATH_STYLE === 'true' ? true : false,
externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined, externalBaseUrl: process.env.STORAGE_EXTERNAL_ENDPOINT || undefined,
endpoint: process.env.STORAGE_S3_ENDPOINT || 'http://localhost:9000', endpoint: process.env.STORAGE_S3_ENDPOINT || 'http://localhost:9000',
region: process.env.STORAGE_REGION || 'us-east-1', region: process.env.STORAGE_REGION || 'us-east-1',
...@@ -59,7 +59,8 @@ export function createDefaultStorageOptions() { ...@@ -59,7 +59,8 @@ export function createDefaultStorageOptions() {
}, },
maxRetries: process.env.STORAGE_S3_MAX_RETRIES maxRetries: process.env.STORAGE_S3_MAX_RETRIES
? parseInt(process.env.STORAGE_S3_MAX_RETRIES) ? parseInt(process.env.STORAGE_S3_MAX_RETRIES)
: 3 : 3,
publicAccessExtraSubPath: process.env.STORAGE_PUBLIC_ACCESS_EXTRA_SUB_PATH || undefined
} satisfies Omit<IAwsS3CompatibleStorageOptions, 'bucket'> & { } satisfies Omit<IAwsS3CompatibleStorageOptions, 'bucket'> & {
publicBucket: string; publicBucket: string;
privateBucket: string; privateBucket: string;
...@@ -82,7 +83,8 @@ export function createDefaultStorageOptions() { ...@@ -82,7 +83,8 @@ export function createDefaultStorageOptions() {
}, },
maxRetries: process.env.STORAGE_S3_MAX_RETRIES maxRetries: process.env.STORAGE_S3_MAX_RETRIES
? parseInt(process.env.STORAGE_S3_MAX_RETRIES) ? parseInt(process.env.STORAGE_S3_MAX_RETRIES)
: 3 : 3,
publicAccessExtraSubPath: process.env.STORAGE_PUBLIC_ACCESS_EXTRA_SUB_PATH || undefined
} satisfies Omit<IAwsS3CompatibleStorageOptions, 'bucket'> & { } satisfies Omit<IAwsS3CompatibleStorageOptions, 'bucket'> & {
publicBucket: string; publicBucket: string;
privateBucket: string; privateBucket: string;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@fastgpt-sdk/storage": "0.6.6", "@fastgpt-sdk/storage": "^0.6.15",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@maxmind/geoip2-node": "^6.3.4", "@maxmind/geoip2-node": "^6.3.4",
"@modelcontextprotocol/sdk": "^1.25.2", "@modelcontextprotocol/sdk": "^1.25.2",
......
...@@ -141,6 +141,7 @@ export const putFileToS3 = async ({ ...@@ -141,6 +141,7 @@ export const putFileToS3 = async ({
maxSize?: number; maxSize?: number;
t: any; t: any;
}) => { }) => {
console.log(headers);
try { try {
const res = await axios.put(url, file, { const res = await axios.put(url, file, {
headers: { headers: {
...@@ -153,6 +154,6 @@ export const putFileToS3 = async ({ ...@@ -153,6 +154,6 @@ export const putFileToS3 = async ({
onSuccess?.(); onSuccess?.();
} }
} catch (error) { } catch (error) {
Promise.reject(parseS3UploadError({ t, error, maxSize })); return Promise.reject(parseS3UploadError({ t, error, maxSize }));
} }
}; };
...@@ -133,8 +133,8 @@ importers: ...@@ -133,8 +133,8 @@ importers:
packages/service: packages/service:
dependencies: dependencies:
'@fastgpt-sdk/storage': '@fastgpt-sdk/storage':
specifier: 0.6.6 specifier: ^0.6.15
version: 0.6.6(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)
'@fastgpt/global': '@fastgpt/global':
specifier: workspace:* specifier: workspace:*
version: link:../global version: link:../global
...@@ -537,8 +537,8 @@ importers: ...@@ -537,8 +537,8 @@ importers:
specifier: 11.11.0 specifier: 11.11.0
version: 11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1) version: 11.11.0(@emotion/react@11.11.1(@types/react@18.3.1)(react@18.3.1))(@types/react@18.3.1)(react@18.3.1)
'@fastgpt-sdk/storage': '@fastgpt-sdk/storage':
specifier: 0.6.6 specifier: ^0.6.15
version: 0.6.6(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)
'@fastgpt/global': '@fastgpt/global':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/global version: link:../../packages/global
...@@ -2608,8 +2608,8 @@ packages: ...@@ -2608,8 +2608,8 @@ packages:
'@fastgpt-sdk/plugin@0.2.17': '@fastgpt-sdk/plugin@0.2.17':
resolution: {integrity: sha512-TU93FD9JIeAV+isoLVVbW+yX14J27Kgd5Sn8LPvYWkrorUEtWeVfd8rOzh/KXPd43hCgD3bSDZ1W3hC06Spnog==} resolution: {integrity: sha512-TU93FD9JIeAV+isoLVVbW+yX14J27Kgd5Sn8LPvYWkrorUEtWeVfd8rOzh/KXPd43hCgD3bSDZ1W3hC06Spnog==}
'@fastgpt-sdk/storage@0.6.6': '@fastgpt-sdk/storage@0.6.15':
resolution: {integrity: sha512-nG+DWwyJS6upfEbJ+FgTc8CGhzpW1JiPEWGO1luhm6lgYRitm0HBBeX3m0j17lYTa2S0ZTDRGJgnXYzOWpYOLg==} resolution: {integrity: sha512-oPbm6EtXQ3ysad/OebF2ovwbIax6PeCvYqA3cGAVEHEJMBU3633ktl1ZaIIkmyjWJLsABZpMf6m7lPBMyISGrA==}
engines: {node: '>=20'} engines: {node: '>=20'}
'@fastify/accept-negotiator@1.1.0': '@fastify/accept-negotiator@1.1.0':
...@@ -14724,7 +14724,7 @@ snapshots: ...@@ -14724,7 +14724,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
'@fastgpt-sdk/storage@0.6.6(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': '@fastgpt-sdk/storage@0.6.15(@opentelemetry/api@1.9.0)(@types/node@20.17.24)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)':
dependencies: dependencies:
'@aws-sdk/client-s3': 3.948.0 '@aws-sdk/client-s3': 3.948.0
'@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0) '@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0)
...@@ -14759,7 +14759,7 @@ snapshots: ...@@ -14759,7 +14759,7 @@ snapshots:
- tsx - tsx
- yaml - yaml
'@fastgpt-sdk/storage@0.6.6(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)': '@fastgpt-sdk/storage@0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1)':
dependencies: dependencies:
'@aws-sdk/client-s3': 3.948.0 '@aws-sdk/client-s3': 3.948.0
'@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0) '@aws-sdk/lib-storage': 3.948.0(@aws-sdk/client-s3@3.948.0)
...@@ -20136,8 +20136,8 @@ snapshots: ...@@ -20136,8 +20136,8 @@ snapshots:
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2) '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2)
eslint: 8.57.1 eslint: 8.57.1
eslint-import-resolver-node: 0.3.9 eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0)(eslint@8.57.1) eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.4(eslint@8.57.1) eslint-plugin-react: 7.37.4(eslint@8.57.1)
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
...@@ -20156,6 +20156,21 @@ snapshots: ...@@ -20156,6 +20156,21 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.0
eslint: 8.57.1
get-tsconfig: 4.10.0
is-bun-module: 1.3.0
oxc-resolver: 5.0.0
stable-hash: 0.0.5
tinyglobby: 0.2.12
optionalDependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0): eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0)(eslint@8.56.0):
dependencies: dependencies:
'@nolyfill/is-core-module': 1.0.39 '@nolyfill/is-core-module': 1.0.39
...@@ -20197,6 +20212,17 @@ snapshots: ...@@ -20197,6 +20212,17 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): eslint-module-utils@2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
...@@ -20237,6 +20263,35 @@ snapshots: ...@@ -20237,6 +20263,35 @@ snapshots:
- eslint-import-resolver-webpack - eslint-import-resolver-webpack
- supports-color - supports-color
eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.6
array.prototype.flat: 1.3.3
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.9.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
minimatch: 3.1.2
object.fromentries: 2.0.8
object.groupby: 1.0.3
object.values: 1.2.1
semver: 6.3.1
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.8.2)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1): eslint-plugin-import@2.31.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.8.2))(eslint-import-resolver-typescript@3.9.0)(eslint@8.57.1):
dependencies: dependencies:
'@rtsao/scc': 1.1.0 '@rtsao/scc': 1.1.0
......
...@@ -46,6 +46,7 @@ STORAGE_EXTERNAL_ENDPOINT= ...@@ -46,6 +46,7 @@ STORAGE_EXTERNAL_ENDPOINT=
STORAGE_S3_ENDPOINT=http://localhost:9000 STORAGE_S3_ENDPOINT=http://localhost:9000
STORAGE_S3_FORCE_PATH_STYLE=true STORAGE_S3_FORCE_PATH_STYLE=true
STORAGE_S3_MAX_RETRIES=3 STORAGE_S3_MAX_RETRIES=3
STORAGE_PUBLIC_ACCESS_EXTRA_SUB_PATH=
# Redis URL # Redis URL
REDIS_URL=redis://default:mypassword@localhost:6379 REDIS_URL=redis://default:mypassword@localhost:6379
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
"@dagrejs/dagre": "^1.1.4", "@dagrejs/dagre": "^1.1.4",
"@emotion/react": "11.11.1", "@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0", "@emotion/styled": "11.11.0",
"@fastgpt-sdk/storage": "0.6.6", "@fastgpt-sdk/storage": "^0.6.15",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@fastgpt/service": "workspace:*", "@fastgpt/service": "workspace:*",
"@fastgpt/web": "workspace:*", "@fastgpt/web": "workspace:*",
......
{ {
"name": "@fastgpt-sdk/storage", "name": "@fastgpt-sdk/storage",
"private": false, "private": false,
"version": "0.6.6", "version": "0.6.15",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
......
...@@ -42,7 +42,7 @@ import type { ...@@ -42,7 +42,7 @@ import type {
import { Upload } from '@aws-sdk/lib-storage'; import { Upload } from '@aws-sdk/lib-storage';
import { EmptyObjectError } from '../errors'; import { EmptyObjectError } from '../errors';
import type { Readable } from 'node:stream'; import type { Readable } from 'node:stream';
import { camelCase, chunk, isNotNil, kebabCase } from 'es-toolkit'; import { camelCase, chunk, isNotNil, kebabCase, trim } from 'es-toolkit';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS } from '../constants'; import { DEFAULT_PRESIGNED_URL_EXPIRED_SECONDS } from '../constants';
...@@ -299,29 +299,42 @@ export class AwsS3StorageAdapter implements IStorage { ...@@ -299,29 +299,42 @@ export class AwsS3StorageAdapter implements IStorage {
} }
} }
if (contentType) { const convertToS3Headers = (meta: Record<string, string>) => {
meta['Content-Type'] = contentType; return Object.keys(meta)
} .filter((key) => key !== 'Content-Type')
.map((key) => `x-amz-meta-${key}`);
};
const url = await getSignedUrl( const url = await getSignedUrl(
this.client, this.client,
new PutObjectCommand({ new PutObjectCommand({
Bucket: this.options.bucket, Bucket: this.options.bucket,
Key: key, Key: key,
Metadata: meta Metadata: meta,
ContentType: contentType
}), }),
{ {
expiresIn expiresIn,
unhoistableHeaders: new Set(convertToS3Headers(meta))
} }
); );
const headers: Record<string, string> = {};
for (const [key, value] of Object.entries(meta)) {
if (key.toLowerCase() === 'content-type') {
continue;
}
headers[`x-amz-meta-${key}`] = value;
}
if (contentType) {
headers['Content-Type'] = contentType;
}
return { return {
key, key,
url: url, url: url,
bucket: this.options.bucket, bucket: this.options.bucket,
metadata: { metadata: headers
'Content-Type': meta['Content-Type']
}
}; };
} }
...@@ -353,10 +366,21 @@ export class AwsS3StorageAdapter implements IStorage { ...@@ -353,10 +366,21 @@ export class AwsS3StorageAdapter implements IStorage {
let url: string; let url: string;
if (this.options.forcePathStyle) { if (this.options.forcePathStyle) {
if (this.options.publicAccessExtraSubPath) {
url = `${this.options.endpoint}/${trim(this.options.publicAccessExtraSubPath, '/')}/${this.options.bucket}/${key}`;
} else {
url = `${this.options.endpoint}/${this.options.bucket}/${key}`; url = `${this.options.endpoint}/${this.options.bucket}/${key}`;
}
} else { } else {
const endpoint = new URL(this.options.endpoint); const endpoint = new URL(this.options.endpoint);
url = `${endpoint.protocol}//${this.options.bucket}.${endpoint.host}/${key}`; const protocol = endpoint.protocol;
const host = endpoint.host;
if (this.options.publicAccessExtraSubPath) {
url = `${protocol}//${this.options.bucket}.${host}/${trim(this.options.publicAccessExtraSubPath, '/')}/${key}`;
} else {
url = `${protocol}//${this.options.bucket}.${host}/${key}`;
}
} }
return { return {
......
...@@ -44,7 +44,9 @@ export class MinioStorageAdapter extends AwsS3StorageAdapter implements IStorage ...@@ -44,7 +44,9 @@ export class MinioStorageAdapter extends AwsS3StorageAdapter implements IStorage
throw new Error('Invalid storage vendor: expected "minio"'); throw new Error('Invalid storage vendor: expected "minio"');
} }
options.forcePathStyle = true; // NOTE:
// Maybe some self-hosted MinIO services don't support path style access,
// options.forcePathStyle = true;
super(options); super(options);
// 解析 endpoint URL // 解析 endpoint URL
...@@ -59,7 +61,7 @@ export class MinioStorageAdapter extends AwsS3StorageAdapter implements IStorage ...@@ -59,7 +61,7 @@ export class MinioStorageAdapter extends AwsS3StorageAdapter implements IStorage
accessKey: options.credentials.accessKeyId, accessKey: options.credentials.accessKeyId,
secretKey: options.credentials.secretAccessKey, secretKey: options.credentials.secretAccessKey,
region: options.region, region: options.region,
pathStyle: true // MinIO 强制使用 path style pathStyle: options.forcePathStyle
}); });
} }
......
...@@ -68,6 +68,14 @@ export interface ICommonStorageOptions { ...@@ -68,6 +68,14 @@ export interface ICommonStorageOptions {
/** SecretAccessKey / SecretKey / SK */ /** SecretAccessKey / SecretKey / SK */
secretAccessKey: string; secretAccessKey: string;
}; };
/**
* 公共访问时额外添加的子路径,可选。
*
* 说明:
* - 用于在公共访问时添加额外的前缀,例如 `/sub-path`。
*/
publicAccessExtraSubPath?: string;
} }
/** /**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment