chore: customDomain openapi doc && new nextapi code snippets (#6082)

* perf: faq

* index

* delete dataset

* delete dataset

* perf: delete dataset

* init

* fix: faq

* refresh

* empty tip

* chore: customDomain openapi doc && new nextapi code snippets

* chore: update doc

* remove ivalid code

* snippets

---------

Co-authored-by: archer <545436317@qq.com>
This commit is contained in:
Finley Ge 2025-12-12 15:28:03 +08:00 committed by GitHub
parent 805cd33a77
commit b22ba1aa53
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 291 additions and 32 deletions

View File

@ -1,35 +1,33 @@
{ {
// Place your FastGPT 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and // Place your FastGPT 工作区 snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are: // used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected. // Placeholders with the same ids are connected.
// Example: // Example:
"Next api template": { "Next api template": {
"scope": "javascript,typescript", "scope": "javascript,typescript",
"prefix": "nextapi", "prefix": "nextapi",
"body": [ "body": [
"import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';", "import { NextAPI } from '@/service/middleware/entry';",
"import { NextAPI } from '@/service/middleware/entry';", "import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';",
"", "",
"export type ${TM_FILENAME_BASE}Query = {};", "async function handler(",
"", " req: ApiRequestProps,",
"export type ${TM_FILENAME_BASE}Body = {};", " res: ApiResponseType<${1}ResponseType>",
"", "): Promise<${1}ResponseType> {",
"export type ${TM_FILENAME_BASE}Response = {};", " const body = ${1}BodySchema.parse(req.body);",
"", " const query = ${1}QuerySchema.parse(req.query);",
"async function handler(", "",
" req: ApiRequestProps<${TM_FILENAME_BASE}Body, ${TM_FILENAME_BASE}Query>,", " ${2}",
" res: ApiResponseType<any>", "",
"): Promise<${TM_FILENAME_BASE}Response> {", " return {};",
" $1", "}",
" return {}", "",
"}", "export default NextAPI(handler);"
"",
"export default NextAPI(handler);"
], ],
"description": "FastGPT Next API template" "description": "FastGPT Next API template with Zod validation"
}, },
"use context template": { "use context template": {
"scope": "typescriptreact", "scope": "typescriptreact",
@ -38,7 +36,7 @@
"import React, { ReactNode } from 'react';", "import React, { ReactNode } from 'react';",
"import { createContext } from 'use-context-selector';", "import { createContext } from 'use-context-selector';",
"", "",
"type ContextType = {$1};", "type ContextType = {${1}};",
"", "",
"export const Context = createContext<ContextType>({});", "export const Context = createContext<ContextType>({});",
"", "",
@ -65,4 +63,4 @@
"});" "});"
] ]
} }
} }

View File

@ -68,3 +68,7 @@ curl --location --request POST 'https://{{host}}/api/admin/initv4144' \
1. 新增 GLM4.6 与 DS3.2 系列模型预设。 1. 新增 GLM4.6 与 DS3.2 系列模型预设。
2. 修复 MinerU SaaS 插件模型版本不能选择 vlm 的问题 2. 修复 MinerU SaaS 插件模型版本不能选择 vlm 的问题
3. 修复微信公众号插件批量上传 markdown 参数传递问题
4. 新增获取微信公众号草稿箱列表工具
5. markdown 转文件支持自定义文件名
6. 修复 import cache 导致的插件无法被更新的问题

View File

@ -2,7 +2,7 @@
"document/content/docs/faq/app.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/faq/app.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/faq/chat.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/faq/chat.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/faq/dataset.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/faq/dataset.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/faq/error.mdx": "2025-12-10T13:24:24+08:00", "document/content/docs/faq/error.mdx": "2025-12-10T20:07:05+08:00",
"document/content/docs/faq/external_channel_integration.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/faq/external_channel_integration.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/faq/index.mdx": "2025-08-02T19:38:37+08:00", "document/content/docs/faq/index.mdx": "2025-08-02T19:38:37+08:00",
"document/content/docs/faq/other.mdx": "2025-08-04T22:07:52+08:00", "document/content/docs/faq/other.mdx": "2025-08-04T22:07:52+08:00",
@ -89,7 +89,7 @@
"document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/google_search_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/plugins/searxng_plugin_guide.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-11-04T16:58:12+08:00", "document/content/docs/introduction/guide/plugins/upload_system_tool.mdx": "2025-11-04T16:58:12+08:00",
"document/content/docs/introduction/guide/team_permissions/customDomain.mdx": "2025-12-10T13:24:24+08:00", "document/content/docs/introduction/guide/team_permissions/customDomain.mdx": "2025-12-10T20:07:05+08:00",
"document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/invitation_link.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/guide/team_permissions/team_roles_permissions.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/introduction/index.en.mdx": "2025-07-23T21:35:03+08:00",
@ -119,7 +119,7 @@
"document/content/docs/upgrading/4-14/4141.mdx": "2025-11-19T10:15:27+08:00", "document/content/docs/upgrading/4-14/4141.mdx": "2025-11-19T10:15:27+08:00",
"document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00", "document/content/docs/upgrading/4-14/4142.mdx": "2025-11-18T19:27:14+08:00",
"document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00", "document/content/docs/upgrading/4-14/4143.mdx": "2025-11-26T20:52:05+08:00",
"document/content/docs/upgrading/4-14/4144.mdx": "2025-12-10T13:28:04+08:00", "document/content/docs/upgrading/4-14/4144.mdx": "2025-12-10T20:07:05+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",
@ -191,7 +191,7 @@
"document/content/docs/use-cases/app-cases/feishu_webhook.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/feishu_webhook.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/fixingEvidence.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/fixingEvidence.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/google_search.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/google_search.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/lab_appointment.mdx": "2025-12-10T13:24:24+08:00", "document/content/docs/use-cases/app-cases/lab_appointment.mdx": "2025-12-10T20:07:05+08:00",
"document/content/docs/use-cases/app-cases/multi_turn_translation_bot.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/multi_turn_translation_bot.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/submit_application_template.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/use-cases/app-cases/submit_application_template.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/use-cases/app-cases/translate-subtitle-using-gpt.mdx": "2025-07-23T21:35:03+08:00", "document/content/docs/use-cases/app-cases/translate-subtitle-using-gpt.mdx": "2025-07-23T21:35:03+08:00",
@ -199,6 +199,6 @@
"document/content/docs/use-cases/external-integration/feishu.mdx": "2025-07-24T14:23:04+08:00", "document/content/docs/use-cases/external-integration/feishu.mdx": "2025-07-24T14:23:04+08:00",
"document/content/docs/use-cases/external-integration/official_account.mdx": "2025-08-05T23:20:39+08:00", "document/content/docs/use-cases/external-integration/official_account.mdx": "2025-08-05T23:20:39+08:00",
"document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00", "document/content/docs/use-cases/external-integration/openapi.mdx": "2025-09-29T11:34:11+08:00",
"document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-10T13:24:24+08:00", "document/content/docs/use-cases/external-integration/wecom.mdx": "2025-12-10T20:07:05+08:00",
"document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00" "document/content/docs/use-cases/index.mdx": "2025-07-24T14:23:04+08:00"
} }

View File

@ -4,6 +4,7 @@ import { ApiKeyPath } from './support/openapi';
import { TagsMap } from './tag'; import { TagsMap } from './tag';
import { PluginPath } from './core/plugin'; import { PluginPath } from './core/plugin';
import { WalletPath } from './support/wallet'; import { WalletPath } from './support/wallet';
import { CustomDomainPath } from './support/customDomain';
export const openAPIDocument = createDocument({ export const openAPIDocument = createDocument({
openapi: '3.1.0', openapi: '3.1.0',
@ -16,7 +17,8 @@ export const openAPIDocument = createDocument({
...ChatPath, ...ChatPath,
...ApiKeyPath, ...ApiKeyPath,
...PluginPath, ...PluginPath,
...WalletPath ...WalletPath,
...CustomDomainPath
}, },
servers: [{ url: '/api' }], servers: [{ url: '/api' }],
'x-tagGroups': [ 'x-tagGroups': [
@ -39,6 +41,10 @@ export const openAPIDocument = createDocument({
{ {
name: '支付', name: '支付',
tags: [TagsMap.walletBill, TagsMap.walletDiscountCoupon] tags: [TagsMap.walletBill, TagsMap.walletDiscountCoupon]
},
{
name: '自定义域名',
tags: [TagsMap.customDomain]
} }
] ]
}); });

View File

@ -0,0 +1,84 @@
import { z } from 'zod';
import { CustomDomainType, ProviderEnum } from '../../../support/customDomain/type';
// Create custom domain
export const CreateCustomDomainBodySchema = z.object({
domain: z.string().meta({ example: 'chat.example.com', description: '自定义域名' }),
provider: ProviderEnum.meta({
example: 'aliyun',
description: 'DNS 提供商aliyun, tencent, volcengine'
}),
cnameDomain: z.string().meta({ example: 'lb.example.com', description: 'CNAME 目标域名' })
});
export type CreateCustomDomainBodyType = z.infer<typeof CreateCustomDomainBodySchema>;
export const CreateCustomDomainResponseSchema = z.object({
success: z.boolean().meta({ example: true, description: '创建是否成功' })
});
export type CreateCustomDomainResponseType = z.infer<typeof CreateCustomDomainResponseSchema>;
// List custom domains
export const CustomDomainListResponseSchema = z.array(
CustomDomainType.extend({
_id: z
.string()
.optional()
.meta({ example: '68ad85a7463006c963799a05', description: '域名记录 ID' })
})
);
export type CustomDomainListResponseType = z.infer<typeof CustomDomainListResponseSchema>;
// Delete custom domain
export const DeleteCustomDomainQuerySchema = z.object({
domain: z.string().meta({ example: 'chat.example.com', description: '要删除的域名' })
});
export type DeleteCustomDomainQueryType = z.infer<typeof DeleteCustomDomainQuerySchema>;
export const DeleteCustomDomainResponseSchema = z.object({
success: z.boolean().meta({ example: true, description: '删除是否成功' })
});
export type DeleteCustomDomainResponseType = z.infer<typeof DeleteCustomDomainResponseSchema>;
// Check DNS resolve
export const CheckDNSResolveBodySchema = z.object({
domain: z.string().meta({ example: 'chat.example.com', description: '要检查的域名' }),
cnameDomain: z.string().meta({ example: 'lb.example.com', description: 'CNAME 目标域名' })
});
export type CheckDNSResolveBodyType = z.infer<typeof CheckDNSResolveBodySchema>;
export const CheckDNSResolveResponseSchema = z.object({
success: z.boolean().meta({ example: true, description: 'DNS 解析是否成功' }),
message: z
.string()
.optional()
.meta({ example: 'CNAME record not resolved', description: '错误信息' })
});
export type CheckDNSResolveResponseType = z.infer<typeof CheckDNSResolveResponseSchema>;
// Active custom domain
export const ActiveCustomDomainBodySchema = z.object({
domain: z.string().meta({ example: 'chat.example.com', description: '要激活的域名' })
});
export type ActiveCustomDomainBodyType = z.infer<typeof ActiveCustomDomainBodySchema>;
export const ActiveCustomDomainResponseSchema = z.object({
success: z.boolean().meta({ example: true, description: '激活是否成功' })
});
export type ActiveCustomDomainResponseType = z.infer<typeof ActiveCustomDomainResponseSchema>;
// Update domain verify file
export const UpdateDomainVerifyFileBodySchema = z.object({
domain: z.string().meta({ example: 'chat.example.com', description: '域名' }),
path: z
.string()
.meta({ example: '/.well-known/pki-validation/fileauth.txt', description: '验证文件路径' }),
content: z.string().meta({ example: '202312121234567890abcdef', description: '验证文件内容' })
});
export type UpdateDomainVerifyFileBodyType = z.infer<typeof UpdateDomainVerifyFileBodySchema>;
export const UpdateDomainVerifyFileResponseSchema = z.object({
success: z.boolean().meta({ example: true, description: '更新是否成功' })
});
export type UpdateDomainVerifyFileResponseType = z.infer<
typeof UpdateDomainVerifyFileResponseSchema
>;

View File

@ -0,0 +1,153 @@
import type { OpenAPIPath } from '../../type';
import {
CreateCustomDomainBodySchema,
CreateCustomDomainResponseSchema,
CustomDomainListResponseSchema,
DeleteCustomDomainQuerySchema,
DeleteCustomDomainResponseSchema,
CheckDNSResolveBodySchema,
CheckDNSResolveResponseSchema,
ActiveCustomDomainBodySchema,
ActiveCustomDomainResponseSchema,
UpdateDomainVerifyFileBodySchema,
UpdateDomainVerifyFileResponseSchema
} from './api';
import { TagsMap } from '../../tag';
export const CustomDomainPath: OpenAPIPath = {
'/proApi/support/customDomain/create': {
post: {
summary: '创建自定义域名',
description:
'创建一个新的自定义域名配置,需要高级套餐权限。创建后域名会自动部署到 K8s 集群中',
tags: [TagsMap.customDomain],
requestBody: {
content: {
'application/json': {
schema: CreateCustomDomainBodySchema
}
}
},
responses: {
200: {
description: '成功创建自定义域名',
content: {
'application/json': {
schema: CreateCustomDomainResponseSchema
}
}
}
}
}
},
'/proApi/support/customDomain/list': {
get: {
summary: '获取自定义域名列表',
description: '获取当前团队的所有自定义域名配置列表',
tags: [TagsMap.customDomain],
responses: {
200: {
description: '成功获取自定义域名列表',
content: {
'application/json': {
schema: CustomDomainListResponseSchema
}
}
}
}
}
},
'/proApi/support/customDomain/delete': {
delete: {
summary: '删除自定义域名',
description: '删除指定的自定义域名配置,同时会从 K8s 集群中移除相关资源',
tags: [TagsMap.customDomain],
requestParams: {
query: DeleteCustomDomainQuerySchema
},
responses: {
200: {
description: '成功删除自定义域名',
content: {
'application/json': {
schema: DeleteCustomDomainResponseSchema
}
}
}
}
}
},
'/proApi/support/customDomain/checkDNSResolve': {
post: {
summary: '检查 DNS 解析',
description: '检查自定义域名的 CNAME 记录是否正确配置和解析',
tags: [TagsMap.customDomain],
requestBody: {
content: {
'application/json': {
schema: CheckDNSResolveBodySchema
}
}
},
responses: {
200: {
description: 'DNS 解析检查结果',
content: {
'application/json': {
schema: CheckDNSResolveResponseSchema
}
}
}
}
}
},
'/proApi/support/customDomain/active': {
post: {
summary: '激活自定义域名',
description: '将自定义域名状态设置为激活,并重新部署到 K8s 集群',
tags: [TagsMap.customDomain],
requestBody: {
content: {
'application/json': {
schema: ActiveCustomDomainBodySchema
}
}
},
responses: {
200: {
description: '成功激活自定义域名',
content: {
'application/json': {
schema: ActiveCustomDomainResponseSchema
}
}
}
}
}
},
'/proApi/support/customDomain/updateVerifyFile': {
post: {
summary: '更新域名验证文件',
description:
'更新域名验证文件配置,用于 SSL 证书验证。更新后会在 K8s 中创建或更新对应的 Ingress',
tags: [TagsMap.customDomain],
requestBody: {
content: {
'application/json': {
schema: UpdateDomainVerifyFileBodySchema
}
}
},
responses: {
200: {
description: '成功更新域名验证文件',
content: {
'application/json': {
schema: UpdateDomainVerifyFileResponseSchema
}
}
}
}
}
}
};

View File

@ -9,6 +9,7 @@ export const TagsMap = {
apiKey: 'APIKey', apiKey: 'APIKey',
walletBill: '订单', walletBill: '订单',
walletDiscountCoupon: '优惠券', walletDiscountCoupon: '优惠券',
customDomain: '自定义域名',
adminDashboard: '管理员仪表盘' adminDashboard: '管理员仪表盘'
}; };

View File

@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { withNextCors } from './cors'; import { withNextCors } from './cors';
import { type ApiRequestProps } from '../../type/next'; import { type ApiRequestProps } from '../../type/next';
import { addLog } from '../system/log'; import { addLog } from '../system/log';
import { ZodError } from 'zod';
export type NextApiHandler<T = any> = ( export type NextApiHandler<T = any> = (
req: ApiRequestProps, req: ApiRequestProps,
@ -49,6 +50,18 @@ export const NextEntry = ({
}); });
} }
} catch (error) { } catch (error) {
// Handle Zod validation errors
if (error instanceof ZodError) {
return jsonRes(res, {
code: 400,
error: {
message: 'Validation error',
details: error.message
},
url: req.url
});
}
return jsonRes(res, { return jsonRes(res, {
code: 500, code: 500,
error, error,