众所周知CF R2提供了10GB了免费空间和每月一千万次的免费Class B操作。
也就是说如果开一个存储桶,并且设置为公开的话,每个月被刷一千万次之后就开始收费,而且上不封顶,没有限制的方法。
为了这个存储桶能够被外界访问到但是同时不会一夜破产,可以创建一个CF Worker把私有桶映射为公开桶+D1数据库记录使用次数的组合作为限制,让一个账单周期月的请求次数限制在选定的范围内
感谢佬友提醒Worker每天100000次之后不一定会停,现在的版本使用D1数据库记录使用次数。
所以每个请求会消耗1个Worker请求次数+一个D1读次数+一个D1写次数
Worker免费套餐的瓶颈依旧在每天100000次,超过了能不能用看Cloudflare心情。
export default {
async fetch(request, env) {
const path = decodeURIComponent(new URL(request.url).pathname.slice(1));
if (!path) {
return new Response("Not Found", { status: 404 });
}
try {
const limitCheckResult = await checkUsageLimit(env);
if (limitCheckResult.limited) {
return new Response("Monthly usage limit exceeded", { status: 429 });
}
const file = await env.OPCT.get(path);
if (!file) {
return new Response("Not Found", { status: 404 });
}
const headers = new Headers();
headers.set('Content-Type', file.httpMetadata?.contentType || 'application/octet-stream');
return new Response(file.body, { headers });
} catch (error) {
return new Response("Server Error", { status: 500 });
}
}
}
async function checkUsageLimit(env) {
const billingResetDay = parseInt(env.BILLING_RESET_DAY);
const monthlyLimit = parseInt(env.MONTHLY_LIMIT);
const billingStartDay = calculateBillingPeriod(billingResetDay);
const usageRecord = await env.DB.prepare(
"SELECT * FROM usage_records LIMIT 1"
).first();
let currentUsage = 1;
if (!usageRecord) {
await env.DB.prepare(
"INSERT INTO usage_records (usage_count, billing_period) VALUES (?, ?)"
).bind(currentUsage, billingStartDay).run();
} else if (usageRecord.billing_period !== billingStartDay) {
await env.DB.prepare(
"UPDATE usage_records SET usage_count = ?, billing_period = ?"
).bind(currentUsage, billingStartDay).run();
} else {
currentUsage = usageRecord.usage_count + 1;
if (currentUsage > monthlyLimit) {
return { limited: true };
}
await env.DB.prepare(
"UPDATE usage_records SET usage_count = ?"
).bind(currentUsage).run();
}
return { limited: false };
}
function calculateBillingPeriod(billingResetDay) {
const now = new Date();
const currentDay = now.getUTCDate();
const currentMonth = now.getUTCMonth() + 1;
const currentYear = now.getUTCFullYear();
const lastDayOfMonth = new Date(now.getUTCFullYear(), now.getUTCMonth() + 1, 0).getUTCDate();
const actualResetDay = Math.min(billingResetDay, lastDayOfMonth);
let targetMonth = currentMonth;
let targetYear = currentYear;
if (currentDay < actualResetDay) {
targetMonth = targetMonth - 1;
if (targetMonth === 0) {
targetMonth = 12;
targetYear = targetYear - 1;
}
}
const lastDayOfPrevMonth = new Date(targetYear, targetMonth, 0).getUTCDate();
const actualPrevMonthResetDay = Math.min(billingResetDay, lastDayOfPrevMonth);
return `${targetYear}-${targetMonth.toString().padStart(2, '0')}-${actualPrevMonthResetDay.toString().padStart(2, '0')}`;
}
设置绑定那里把桶绑定到OPCT名称上就行,D1数据库绑定到DB
数据库创建在console那里填入:
CREATE TABLE usage_records (
usage_count INTEGER NOT NULL,
billing_period TEXT NOT NULL
);
环境变量:
BILLING_RESET_DAY:R2的账单重置日,在账单那里看
MONTHLY_LIMIT:每月请求次数限制
另外Worker的放置位置保持在默认就行,选择智能的话反而Worker全都运行在桶的地理位置
另外如果不是为了白嫖,那Backblaze B2的存储价格低一些