guanghulab/scripts/quota-middleware.js

153 lines
4.4 KiB
JavaScript
Raw Normal View History

/**
* 天眼配额守卫中间件
* 拦截所有 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 };