guanghulab/scripts/quota-middleware.js
2026-05-10 13:12:44 +08:00

153 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 天眼配额守卫中间件
* 拦截所有 API 调用请求,检查配额后放行或拒绝
*
* 指令ZY-AGEOS-TOWER-2026-0326-001
* 版权:国作登字-2026-A-00037559
*/
const fs = require('fs');
const path = require('path');
const QUOTA_CONFIG_PATH = path.join(__dirname, '..', 'skyeye', 'guards', 'quota-guard.json');
const QUOTA_LOG_PATH = path.join(__dirname, '..', '.github', 'persona-brain', 'quota-daily.json');
let _quotaConfig = null;
function getQuotaConfig() {
if (!_quotaConfig) {
_quotaConfig = JSON.parse(fs.readFileSync(QUOTA_CONFIG_PATH, 'utf8'));
}
return _quotaConfig;
}
function loadDailyQuota() {
const today = new Date().toISOString().split('T')[0];
try {
const data = JSON.parse(fs.readFileSync(QUOTA_LOG_PATH, 'utf8'));
if (data.date === today) return data;
} catch (e) {
// File missing or corrupted — will reset to fresh daily quota
}
return {
date: today,
total_used: 0,
by_member: {},
by_tier: { P0: 0, P1: 0, P2: 0 }
};
}
function saveDailyQuota(daily) {
const dir = path.dirname(QUOTA_LOG_PATH);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(QUOTA_LOG_PATH, JSON.stringify(daily, null, 2));
}
function getTier(devId) {
const config = getQuotaConfig();
for (const [tier, tierConfig] of Object.entries(config.priority_tiers)) {
if (tierConfig.members && tierConfig.members.includes(devId)) return tier;
}
return null; // unknown developer — not in any tier
}
function checkQuota(devId) {
const config = getQuotaConfig();
const daily = loadDailyQuota();
const tier = getTier(devId);
if (!tier) {
return {
allowed: false,
reason: '开发者 ' + devId + ' 未注册到任何配额层级',
suggestion: '请联系管理员将你添加到配额层级中'
};
}
const tierConfig = config.priority_tiers[tier];
const totalDaily = config.quota_pool.daily_total;
// If daily_total is not yet configured, allow through
if (totalDaily === 'auto-detect' || typeof totalDaily !== 'number') {
return { allowed: true, remaining: -1, note: 'quota pool daily_total not yet configured' };
}
// Calculate tier allocation
const tierAllocation = Math.floor(totalDaily * tierConfig.allocation_percent / 100);
const tierUsed = daily.by_tier[tier] || 0;
if (tierUsed >= tierAllocation) {
return {
allowed: false,
reason: `${tier} 层级今日配额已耗尽(${tierUsed}/${tierAllocation}`,
suggestion: tier === 'P2' ? '请等待次日刷新或联系冰朔' : '请联系冰朔调整配额'
};
}
// Per-member cap check
const memberCount = tierConfig.members ? tierConfig.members.length : 1;
const perMemberCap = Math.floor(tierAllocation / memberCount);
const memberUsed = daily.by_member[devId] || 0;
if (memberUsed >= perMemberCap) {
return {
allowed: false,
reason: `你的个人今日配额已耗尽(${memberUsed}/${perMemberCap}`,
suggestion: '请等待次日刷新'
};
}
return { allowed: true, remaining: perMemberCap - memberUsed };
}
function recordUsage(devId) {
const daily = loadDailyQuota();
const tier = getTier(devId);
daily.total_used = (daily.total_used || 0) + 1;
daily.by_member[devId] = (daily.by_member[devId] || 0) + 1;
daily.by_tier[tier] = (daily.by_tier[tier] || 0) + 1;
saveDailyQuota(daily);
}
function generateDailyReport() {
const config = getQuotaConfig();
const daily = loadDailyQuota();
const totalDaily = config.quota_pool.daily_total;
const report = {
date: daily.date,
total: {
used: daily.total_used || 0,
limit: totalDaily,
percent: totalDaily !== 'auto-detect' && typeof totalDaily === 'number'
? Math.round(((daily.total_used || 0) / totalDaily) * 100)
: 'N/A'
},
tiers: {},
members: daily.by_member || {}
};
for (const [tier, tierConfig] of Object.entries(config.priority_tiers)) {
const tierUsed = daily.by_tier[tier] || 0;
const allocation = typeof totalDaily === 'number'
? Math.floor(totalDaily * tierConfig.allocation_percent / 100)
: 'N/A';
report.tiers[tier] = {
name: tierConfig.name,
used: tierUsed,
allocation: allocation,
percent: typeof allocation === 'number' ? Math.round((tierUsed / allocation) * 100) : 'N/A'
};
}
return report;
}
module.exports = { checkQuota, recordUsage, getTier, loadDailyQuota, generateDailyReport };