Commit ca274feb by Archer Committed by GitHub

perf: init s3 (#5795)

* fix: variables refresh

* fix: workflow start check

* perf: init s3
parent 5054ccd4
...@@ -3,14 +3,40 @@ title: 'V4.13.2(进行中)' ...@@ -3,14 +3,40 @@ title: 'V4.13.2(进行中)'
description: 'FastGPT V4.13.2 更新说明' description: 'FastGPT V4.13.2 更新说明'
--- ---
# 更新指南 ## 更新指南
## 增加 FastGPT/FastGPT-pro 环境变量 ### 1. 更新镜像:
- 更新 FastGPT 镜像tag: v4.13.2
- 更新 FastGPT 商业版镜像tag: v4.13.2
- 更新 fastgpt-plugin 镜像 tag: v0.2.4
- mcp_server 无需更新
- Sandbox 无需更新
- AIProxy 无需更新
### 2. 增加 FastGPT/FastGPT-pro 环境变量
``` ```
S3_PUBLIC_BUCKET=S3公开桶名称(公开读私有写) S3_PUBLIC_BUCKET=fastgpt_public #(公开读公开桶名称,对应原来 plugin 项目的S3_TOOL_BUCKET)
S3_PRIVATE_BUCKET=fastgpt_private #(私有读私有写桶名称,对应原来 plugin 项目的S3_PLUGIN_BUCKET)
```
### 3. 修复 fastgpt-plugin 环境变量
- S3_TOOL_BUCKET 改名成 S3_PUBLIC_BUCKET
- S3_PLUGIN_BUCKET 改名成 S3_PRIVATE_BUCKET
### 4. 执行升级脚本
从任意终端,发起 1 个 HTTP 请求。其中 `{{rootkey}}` 替换成环境变量里的 `rootkey`;`{{host}}` 替换成**FastGPT 域名**。
```bash
curl --location --request POST 'https://{{host}}/api/admin/initv4132' \
--header 'rootkey: {{rootkey}}' \
--header 'Content-Type: application/json'
``` ```
会删除原先 S3 的 circleLife 策略。如果使用的是外部 S3,可能会因为不支持 circleLife 操作导
## 🚀 新增内容 ## 🚀 新增内容
1. HTTP 工具集支持手动创建模式。 1. HTTP 工具集支持手动创建模式。
......
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
"document/content/docs/upgrading/4-12/4124.mdx": "2025-09-17T22:29:56+08:00", "document/content/docs/upgrading/4-12/4124.mdx": "2025-09-17T22:29:56+08:00",
"document/content/docs/upgrading/4-13/4130.mdx": "2025-09-30T16:00:10+08:00", "document/content/docs/upgrading/4-13/4130.mdx": "2025-09-30T16:00:10+08:00",
"document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00", "document/content/docs/upgrading/4-13/4131.mdx": "2025-09-30T15:47:06+08:00",
"document/content/docs/upgrading/4-13/4132.mdx": "2025-10-17T21:40:12+08:00", "document/content/docs/upgrading/4-13/4132.mdx": "2025-10-20T19:08:21+08:00",
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",
......
import { Client, type RemoveOptions, type CopyConditions, type LifecycleConfig } from 'minio'; import { Client, type RemoveOptions, type CopyConditions } from 'minio';
import { import {
type ExtensionType,
type CreatePostPresignedUrlOptions, type CreatePostPresignedUrlOptions,
type CreatePostPresignedUrlParams, type CreatePostPresignedUrlParams,
type CreatePostPresignedUrlResult, type CreatePostPresignedUrlResult,
...@@ -9,9 +8,9 @@ import { ...@@ -9,9 +8,9 @@ import {
import { defaultS3Options, Mimes } from '../constants'; import { defaultS3Options, Mimes } from '../constants';
import path from 'node:path'; import path from 'node:path';
import { MongoS3TTL } from '../schema'; import { MongoS3TTL } from '../schema';
import { UserError } from '@fastgpt/global/common/error/utils';
import { getNanoid } from '@fastgpt/global/common/string/tools'; import { getNanoid } from '@fastgpt/global/common/string/tools';
import { addHours } from 'date-fns'; import { addHours } from 'date-fns';
import { addLog } from '../../system/log';
export class S3BaseBucket { export class S3BaseBucket {
private _client: Client; private _client: Client;
...@@ -56,6 +55,7 @@ export class S3BaseBucket { ...@@ -56,6 +55,7 @@ export class S3BaseBucket {
await this.client.makeBucket(this.bucketName); await this.client.makeBucket(this.bucketName);
} }
await this.options.afterInit?.(); await this.options.afterInit?.();
console.log(`S3 init success: ${this.name}`);
}; };
init(); init();
} }
...@@ -63,8 +63,10 @@ export class S3BaseBucket { ...@@ -63,8 +63,10 @@ export class S3BaseBucket {
get name(): string { get name(): string {
return this.bucketName; return this.bucketName;
} }
get client(): Client {
protected get client(): Client { return this._client;
}
get externalClient(): Client {
return this._externalClient ?? this._client; return this._externalClient ?? this._client;
} }
...@@ -93,9 +95,9 @@ export class S3BaseBucket { ...@@ -93,9 +95,9 @@ export class S3BaseBucket {
try { try {
const { expiredHours } = options; const { expiredHours } = options;
const filename = params.filename; const filename = params.filename;
const ext = path.extname(filename).toLowerCase() as ExtensionType; const ext = path.extname(filename).toLowerCase();
const contentType = Mimes[ext] ?? 'application/octet-stream'; const contentType = Mimes[ext as keyof typeof Mimes] ?? 'application/octet-stream';
const maxFileSize = this.options.maxFileSize as number; const maxFileSize = this.options.maxFileSize;
const key = (() => { const key = (() => {
if ('rawKey' in params) return params.rawKey; if ('rawKey' in params) return params.rawKey;
...@@ -103,20 +105,21 @@ export class S3BaseBucket { ...@@ -103,20 +105,21 @@ export class S3BaseBucket {
return `${params.source}/${params.teamId}/${getNanoid(6)}-${filename}`; return `${params.source}/${params.teamId}/${getNanoid(6)}-${filename}`;
})(); })();
const policy = this.client.newPostPolicy(); const policy = this.externalClient.newPostPolicy();
policy.setKey(key); policy.setKey(key);
policy.setBucket(this.name); policy.setBucket(this.name);
policy.setContentType(contentType); policy.setContentType(contentType);
policy.setContentLengthRange(1, maxFileSize); if (maxFileSize) {
policy.setContentLengthRange(1, maxFileSize);
}
policy.setExpires(new Date(Date.now() + 10 * 60 * 1000)); policy.setExpires(new Date(Date.now() + 10 * 60 * 1000));
policy.setUserMetaData({ policy.setUserMetaData({
'content-type': contentType,
'content-disposition': `attachment; filename="${encodeURIComponent(filename)}"`, 'content-disposition': `attachment; filename="${encodeURIComponent(filename)}"`,
'origin-filename': encodeURIComponent(filename), 'origin-filename': encodeURIComponent(filename),
'upload-time': new Date().toISOString() 'upload-time': new Date().toISOString()
}); });
const { formData, postURL } = await this.client.presignedPostPolicy(policy); const { formData, postURL } = await this.externalClient.presignedPostPolicy(policy);
if (expiredHours) { if (expiredHours) {
await MongoS3TTL.create({ await MongoS3TTL.create({
...@@ -131,7 +134,8 @@ export class S3BaseBucket { ...@@ -131,7 +134,8 @@ export class S3BaseBucket {
fields: formData fields: formData
}; };
} catch (error) { } catch (error) {
return Promise.reject(error); addLog.error('Failed to create post presigned url', error);
return Promise.reject('Failed to create post presigned url');
} }
} }
} }
import { NextAPI } from '@/service/middleware/entry';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { type NextApiRequest, type NextApiResponse } from 'next';
import { S3Buckets } from '@fastgpt/service/common/s3/constants';
// 将 S3 原先的 circleLife 策略全部去掉
async function handler(req: NextApiRequest, _res: NextApiResponse) {
await authCert({ req, authRoot: true });
if (!global.s3BucketMap[S3Buckets.public]) {
return Promise.reject('S3 not initialized');
}
await global.s3BucketMap[S3Buckets.public].client.removeBucketLifecycle(S3Buckets.public);
await global.s3BucketMap[S3Buckets.private].client.removeBucketLifecycle(S3Buckets.private);
return {};
}
export default NextAPI(handler);
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