Skip to content

Commit 7b31671

Browse files
committed
feat: team rate limitation
1 parent 48c0c15 commit 7b31671

File tree

3 files changed

+62
-1
lines changed

3 files changed

+62
-1
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { getGlobalRedisConnection } from '../../common/redis';
2+
import { jsonRes } from '../../common/response';
3+
import type { NextApiResponse } from 'next';
4+
5+
type FrequencyLimitOption = {
6+
teamId: string;
7+
seconds: number;
8+
limit: number;
9+
keyPrefix: string;
10+
res: NextApiResponse;
11+
};
12+
13+
export const teamFrequencyLimit = async ({
14+
teamId,
15+
seconds,
16+
limit,
17+
keyPrefix,
18+
res
19+
}: FrequencyLimitOption) => {
20+
const redis = getGlobalRedisConnection();
21+
const key = `${keyPrefix}:${teamId}`;
22+
23+
const result = await redis
24+
.multi()
25+
.incr(key)
26+
.expire(key, seconds, 'NX') // 只在key不存在时设置过期时间
27+
.exec();
28+
29+
if (!result) {
30+
return Promise.reject(new Error('Redis connection error'));
31+
}
32+
33+
const currentCount = result[0][1] as number;
34+
35+
if (currentCount > limit) {
36+
const remainingTime = await redis.ttl(key);
37+
jsonRes(res, {
38+
code: 429,
39+
error: `Rate limit exceeded. Maximum ${limit} requests per ${seconds} seconds for this team. Please try again in ${remainingTime} seconds.`
40+
});
41+
return false;
42+
}
43+
44+
// 在响应头中添加限流信息
45+
res.setHeader('X-RateLimit-Limit', limit);
46+
res.setHeader('X-RateLimit-Remaining', Math.max(0, limit - currentCount));
47+
res.setHeader('X-RateLimit-Reset', Date.now() + seconds * 1000);
48+
return true;
49+
};

packages/service/common/api/teamFrequencyLimit.ts

Whitespace-only changes.

projects/app/src/pages/api/v1/chat/completions.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import {
3333
removeEmptyUserInput
3434
} from '@fastgpt/global/core/chat/utils';
3535
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
36-
import { getUserChatInfo } from '@fastgpt/service/support/user/team/utils';
3736
import { getRunningUserInfoByTmbId } from '@fastgpt/service/support/user/team/utils';
3837
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
3938
import { MongoApp } from '@fastgpt/service/core/app/schema';
@@ -61,6 +60,7 @@ import { getWorkflowToolInputsFromStoreNodes } from '@fastgpt/global/core/app/to
6160
import { UserError } from '@fastgpt/global/common/error/utils';
6261
import { getLocale } from '@fastgpt/service/common/middle/i18n';
6362
import { formatTime2YMDHM } from '@fastgpt/global/common/string/time';
63+
import { teamFrequencyLimit } from '@fastgpt/service/common/api/frequencyLimit';
6464

6565
type FastGptWebChatProps = {
6666
chatId?: string; // undefined: get histories from messages, '': new chat, 'xxxxx': get histories from db
@@ -185,6 +185,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
185185
chatId
186186
});
187187
})();
188+
189+
if (
190+
!(await teamFrequencyLimit({
191+
teamId,
192+
keyPrefix: 'chat:completions',
193+
seconds: 60,
194+
limit: 5000,
195+
res
196+
}))
197+
) {
198+
return {};
199+
}
188200
retainDatasetCite = retainDatasetCite && !!responseDetail;
189201
const isPlugin = app.type === AppTypeEnum.workflowTool;
190202

0 commit comments

Comments
 (0)