'use strict'; const moment = require('moment'); const { reportFastgptResponse } = require('../services/dashboardReporter'); // 从运行时上下文中获取额度/计费服务实例。 const getQuotaService = (ctx) => ctx?.app?.fs?.quotaBillingService; // 记录一次生成请求的 token 使用量,并按规则完成免费判定与扣费结算。 const trackGenerateUsage = async (ctx, { creator, docKind, docId, chapterKey = null, responseBody, analyticsApplicationId = '', analyticsAppKey = '', ignoreCreateFree = false, allowChapterFirstFree = true, }) => { try { const quotaService = getQuotaService(ctx); if (!quotaService || !docKind) return; const userId = quotaService.normalizeUserId(creator); const normalizedDocId = Number(docId); if (!userId || !Number.isFinite(normalizedDocId) || normalizedDocId <= 0) return; const usage = quotaService.toUsageFromResponse(responseBody || {}); let billingStatus = quotaService.BILLING_STATUS.SKIP; let amountYuan = 0; let requestId = null; const docIsCreateFree = ignoreCreateFree ? false : await quotaService.isDocGenerateBillingExemptByCreate({ userId, docId: normalizedDocId, docKind, }); // 仅在存在有效 token 消耗时进入计费流程。 if (usage.totalTokens > 0 && !docIsCreateFree) { let isFree = false; // 章节级首免:首次生成不扣费,仅记录免费状态。 if (chapterKey && allowChapterFirstFree) { isFree = await quotaService.markChapterFirstFree({ userId, docId: normalizedDocId, chapterKey: String(chapterKey), }); } if (isFree) { billingStatus = quotaService.BILLING_STATUS.FREE; } else { billingStatus = quotaService.BILLING_STATUS.PENDING; amountYuan = quotaService.calculateAmountYuan(usage.totalTokens); requestId = quotaService.createRequestId({ userId, docKind, docId: normalizedDocId, chapterKey: chapterKey ? String(chapterKey) : '', }); } } const eventId = await quotaService.recordGenerateEvent({ userId, docId: normalizedDocId, docKind, chapterKey: chapterKey ? String(chapterKey) : null, usage, billingStatus, amountYuan, requestId, }); // 仅对待结算记录执行实际扣费,避免重复计费。 if (billingStatus === quotaService.BILLING_STATUS.PENDING && eventId) { const aideductUserId = await quotaService.resolveChargeUserId({ userId, externalUserId: ctx?.fs?.userIdMapping?.externalUserId || '', }); await quotaService.settleGenerateBilling({ eventId, aideductUserId, amountYuan, requestId, logger: ctx.logger, }); } if (analyticsApplicationId && responseBody) { await reportFastgptResponse({ ctx, applicationId: analyticsApplicationId, actionId: `quota:${docKind}:${normalizedDocId}:${chapterKey || eventId || Date.now()}`, responseBody, appKey: analyticsAppKey, userId, // Full-document generation reports one business call after all // chapters succeed; individual chapter responses only carry usage. includeCall: !chapterKey, }); } } catch (error) { ctx?.logger?.log?.(error); } }; // 写入 AI 查询日志(用于行为追踪与审计),支持复用外部事务。 const recordAiQueryLog = async (ctx, { userId, ipAddress, feature, clientId = 'pep-feixiaoshang', time = null, transaction = null, }) => { try { const models = ctx?.app?.fs?.dc?.models; if (!models?.AiQueryRecord || !userId || !feature) return; const createOptions = transaction ? { transaction } : undefined; const payload = { userId, ipAddress, clientId, feature, time: time || moment(), }; await models.AiQueryRecord.create(payload, createOptions); } catch (error) { ctx?.logger?.log?.(error); } }; module.exports = { getQuotaService, trackGenerateUsage, recordAiQueryLog, };