team qpm limit & plan tracks

This commit is contained in:
heheer 2025-12-09 16:56:06 +08:00 committed by archer
parent 744fc925f6
commit 669636d9e2
No known key found for this signature in database
GPG Key ID: 4446499B846D4A9E
6 changed files with 90 additions and 10 deletions

View File

@ -10,5 +10,7 @@ export enum TrackEnum {
readSystemAnnouncement = 'readSystemAnnouncement',
clickOperationalAd = 'clickOperationalAd',
closeOperationalAd = 'closeOperationalAd',
teamChatQPM = 'teamChatQPM'
teamChatQPM = 'teamChatQPM',
subscriptionDeleted = 'subscriptionDeleted',
freeAccountCleanup = 'freeAccountCleanup'
}

View File

@ -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<number> => {
// 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}`;

View File

@ -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
}
});
}
};

View File

@ -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 (

View File

@ -10,7 +10,9 @@ export enum TimerIdEnum {
clearExpiredRawTextBuffer = 'clearExpiredRawTextBuffer',
clearExpiredDatasetImage = 'clearExpiredDatasetImage',
clearExpiredMinioFiles = 'clearExpiredMinioFiles',
recordTeamQPM = 'recordTeamQPM'
recordTeamQPM = 'recordTeamQPM',
auditLogCleanup = 'auditLogCleanup',
chatHistoryCleanup = 'chatHistoryCleanup'
}
export enum LockNotificationEnum {

View File

@ -278,3 +278,17 @@ export const getTeamPoints = async ({ teamId }: { teamId: string }) => {
usedPoints: planStatus.usedPoints
};
};
export const getCachedTeamQPMLimit = async (teamId: string): Promise<number | null> => {
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<void> => {
const cacheKey = `${CacheKeyEnum.team_qpm_limit}:${teamId}`;
await setRedisCache(cacheKey, limit.toString(), CacheKeyEnumTime.team_qpm_limit);
};
export const clearTeamQPMLimitCache = async (teamId: string): Promise<void> => {
const cacheKey = `${CacheKeyEnum.team_qpm_limit}:${teamId}`;
await delRedisCache(cacheKey);
};