From 036c37855f9b8655f7a7a71090be7a3fea69424c Mon Sep 17 00:00:00 2001 From: heheer Date: Tue, 9 Dec 2025 16:56:06 +0800 Subject: [PATCH] team qpm limit & plan tracks --- .../global/common/middle/tracks/constants.ts | 4 +- packages/service/common/api/frequencyLimit.ts | 39 ++++++++++++++++--- .../service/common/middle/tracks/utils.ts | 33 ++++++++++++++++ packages/service/common/redis/cache.ts | 6 ++- .../common/system/timerLock/constants.ts | 4 +- packages/service/support/wallet/sub/utils.ts | 14 +++++++ 6 files changed, 90 insertions(+), 10 deletions(-) diff --git a/packages/global/common/middle/tracks/constants.ts b/packages/global/common/middle/tracks/constants.ts index 8aec419f0..62cc1cc4e 100644 --- a/packages/global/common/middle/tracks/constants.ts +++ b/packages/global/common/middle/tracks/constants.ts @@ -10,5 +10,7 @@ export enum TrackEnum { readSystemAnnouncement = 'readSystemAnnouncement', clickOperationalAd = 'clickOperationalAd', closeOperationalAd = 'closeOperationalAd', - teamChatQPM = 'teamChatQPM' + teamChatQPM = 'teamChatQPM', + subscriptionDeleted = 'subscriptionDeleted', + freeAccountCleanup = 'freeAccountCleanup' } diff --git a/packages/service/common/api/frequencyLimit.ts b/packages/service/common/api/frequencyLimit.ts index e2b88eb67..60fdb1528 100644 --- a/packages/service/common/api/frequencyLimit.ts +++ b/packages/service/common/api/frequencyLimit.ts @@ -2,15 +2,19 @@ import { getGlobalRedisConnection } from '../../common/redis'; import { jsonRes } from '../../common/response'; import type { NextApiResponse } from 'next'; +import { + getCachedTeamQPMLimit, + setCachedTeamQPMLimit, + getTeamPlanStatus +} from '../../support/wallet/sub/utils'; +import { SubTypeEnum } from '@fastgpt/global/support/wallet/sub/constants'; export enum LimitTypeEnum { chat = 'chat' } -const limitMap = { - [LimitTypeEnum.chat]: { - seconds: 60, - limit: Number(process.env.CHAT_MAX_QPM || 5000) - } + +const limitSecondsMap = { + [LimitTypeEnum.chat]: 60 }; type FrequencyLimitOption = { @@ -19,8 +23,31 @@ type FrequencyLimitOption = { res: NextApiResponse; }; +// Get team's dynamic QPM limit with caching +export const getTeamQPMLimit = async (teamId: string): Promise => { + // 1. Try to get from cache first + const cachedLimit = await getCachedTeamQPMLimit(teamId); + if (cachedLimit !== null) { + return cachedLimit; + } + + // 2. Cache miss, compute from database + const teamPlanStatus = await getTeamPlanStatus({ teamId }); + const limit = + teamPlanStatus[SubTypeEnum.standard]?.requestsPerMinute ?? + teamPlanStatus.standardConstants?.requestsPerMinute ?? + 30; + + // 3. Write to cache + await setCachedTeamQPMLimit(teamId, limit); + + return limit; +}; + export const teamFrequencyLimit = async ({ teamId, type, res }: FrequencyLimitOption) => { - const { seconds, limit } = limitMap[type]; + const limit = await getTeamQPMLimit(teamId); + const seconds = limitSecondsMap[type]; + const redis = getGlobalRedisConnection(); const key = `frequency:${type}:${teamId}`; diff --git a/packages/service/common/middle/tracks/utils.ts b/packages/service/common/middle/tracks/utils.ts index 1d5127fb8..61ec076f4 100644 --- a/packages/service/common/middle/tracks/utils.ts +++ b/packages/service/common/middle/tracks/utils.ts @@ -156,5 +156,38 @@ export const pushTrack = { teamId: data.teamId } }); + }, + subscriptionDeleted: (data: { + teamId: string; + subscriptionType: string; + subLevel?: string; + totalPoints: number; + usedPoints: number; + startTime: Date; + expiredTime: Date; + }) => { + return createTrack({ + event: TrackEnum.subscriptionDeleted, + data: { + teamId: data.teamId, + subscriptionType: data.subscriptionType, + subLevel: data.subLevel, + totalPoints: data.totalPoints, + usedPoints: data.usedPoints, + activeDays: Math.ceil( + (new Date(data.expiredTime).getTime() - new Date(data.startTime).getTime()) / + (1000 * 60 * 60 * 24) + ) + } + }); + }, + freeAccountCleanup: (data: { teamId: string; expiredTime: Date }) => { + return createTrack({ + event: TrackEnum.freeAccountCleanup, + data: { + teamId: data.teamId, + expiredTime: data.expiredTime + } + }); } }; diff --git a/packages/service/common/redis/cache.ts b/packages/service/common/redis/cache.ts index bcd496a6c..523beef9f 100644 --- a/packages/service/common/redis/cache.ts +++ b/packages/service/common/redis/cache.ts @@ -8,14 +8,16 @@ const getCacheKey = (key: string) => `${redisPrefix}${key}`; export enum CacheKeyEnum { team_vector_count = 'team_vector_count', team_point_surplus = 'team_point_surplus', - team_point_total = 'team_point_total' + team_point_total = 'team_point_total', + team_qpm_limit = 'team_qpm_limit' } // Seconds export enum CacheKeyEnumTime { team_vector_count = 30 * 60, team_point_surplus = 1 * 60, - team_point_total = 1 * 60 + team_point_total = 1 * 60, + team_qpm_limit = 60 * 60 } export const setRedisCache = async ( diff --git a/packages/service/common/system/timerLock/constants.ts b/packages/service/common/system/timerLock/constants.ts index 600b249ba..2dd014fc5 100644 --- a/packages/service/common/system/timerLock/constants.ts +++ b/packages/service/common/system/timerLock/constants.ts @@ -10,7 +10,9 @@ export enum TimerIdEnum { clearExpiredRawTextBuffer = 'clearExpiredRawTextBuffer', clearExpiredDatasetImage = 'clearExpiredDatasetImage', clearExpiredMinioFiles = 'clearExpiredMinioFiles', - recordTeamQPM = 'recordTeamQPM' + recordTeamQPM = 'recordTeamQPM', + auditLogCleanup = 'auditLogCleanup', + chatHistoryCleanup = 'chatHistoryCleanup' } export enum LockNotificationEnum { diff --git a/packages/service/support/wallet/sub/utils.ts b/packages/service/support/wallet/sub/utils.ts index 434d258d8..1a27f1018 100644 --- a/packages/service/support/wallet/sub/utils.ts +++ b/packages/service/support/wallet/sub/utils.ts @@ -278,3 +278,17 @@ export const getTeamPoints = async ({ teamId }: { teamId: string }) => { usedPoints: planStatus.usedPoints }; }; + +export const getCachedTeamQPMLimit = async (teamId: string): Promise => { + const cacheKey = `${CacheKeyEnum.team_qpm_limit}:${teamId}`; + const cached = await getRedisCache(cacheKey); + return cached ? Number(cached) : null; +}; +export const setCachedTeamQPMLimit = async (teamId: string, limit: number): Promise => { + const cacheKey = `${CacheKeyEnum.team_qpm_limit}:${teamId}`; + await setRedisCache(cacheKey, limit.toString(), CacheKeyEnumTime.team_qpm_limit); +}; +export const clearTeamQPMLimitCache = async (teamId: string): Promise => { + const cacheKey = `${CacheKeyEnum.team_qpm_limit}:${teamId}`; + await delRedisCache(cacheKey); +};