Files
blogflare/src/lib/server/cloudflare.ts
T

173 lines
4.2 KiB
TypeScript
Raw 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.
/* eslint-disable @typescript-eslint/no-explicit-any */
export async function purgeCache(platform: App.Platform | undefined, paths: string[]) {
// @ts-expect-error Cloudflare env types
const token = platform?.env?.CF_API_TOKEN;
// @ts-expect-error Cloudflare env types
const zoneId = platform?.env?.CF_ZONE_ID;
const domain = platform?.env?.CF_DOMAIN; // 必须是带协议的绝对域名,如 https://blog.example.com
if (!token || !zoneId) {
console.warn('未配置 CF_API_TOKEN 或 CF_ZONE_ID,跳过清除 Cloudflare 缓存');
return;
}
if (!domain) {
console.warn('未配置 CF_DOMAIN,无法精准拼接 URL,跳过清除缓存以保护其他项目。');
return;
}
// 将路径转换为带域名的绝对 URL,Cloudflare 的 purge_cache(files) 要求必须为绝对 URL
const urls = paths.map((p) => {
// 确保拼接正确,防止出现双斜杠或少斜杠
const cleanDomain = domain.replace(/\/$/, '');
const cleanPath = p.startsWith('/') ? p : `/${p}`;
return `${cleanDomain}${cleanPath}`;
});
try {
const body = { files: urls };
const response = await fetch(
`https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(body)
}
);
const result = (await response.json()) as any;
if (result.success) {
console.log(
'✅ 成功清除 Cloudflare 缓存',
urls ? `(URLs: ${urls.join(', ')})` : '(全部缓存)'
);
} else {
console.error('❌ 清除缓存失败:', result.errors);
}
} catch (error) {
console.error('❌ 调用 Cloudflare Purge API 时发生异常:', error);
}
}
export async function fetchAnalytics(platform: App.Platform | undefined) {
// @ts-expect-error Cloudflare env types
const token = platform?.env?.CF_API_TOKEN;
// @ts-expect-error Cloudflare env types
const zoneId = platform?.env?.CF_ZONE_ID;
// @ts-expect-error Cloudflare env types
const accountId = platform?.env?.CF_ACCOUNT_ID;
// @ts-expect-error Cloudflare env types
const d1Id = platform?.env?.CF_D1_ID;
// @ts-expect-error Cloudflare env types
const r2Bucket = platform?.env?.CF_R2_BUCKET;
if (!token || !zoneId) {
return null;
}
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
let accountQuery = '';
if (accountId) {
let d1Query = '';
if (d1Id) {
d1Query = `
d1AnalyticsAdaptiveGroups(
limit: 10000,
filter: { datetime_geq: "${thirtyDaysAgo}T00:00:00Z", databaseId: "${d1Id}" }
) {
sum {
readQueries
writeQueries
rowsRead
rowsWritten
}
}
`;
}
let r2Query = '';
if (r2Bucket) {
r2Query = `
r2StorageAdaptiveGroups(
limit: 1,
filter: { datetime_geq: "${thirtyDaysAgo}T00:00:00Z", bucketName: "${r2Bucket}" },
orderBy: [datetime_DESC]
) {
sum {
payloadSize
}
}
`;
}
if (d1Query || r2Query) {
accountQuery = `
accounts(filter: { accountTag: "${accountId}" }) {
${d1Query}
${r2Query}
}
`;
}
}
const query = `
query {
viewer {
zones(filter: { zoneTag: "${zoneId}" }) {
httpRequests1dGroups(
limit: 30,
filter: { date_gt: "${thirtyDaysAgo}" }
orderBy: [date_ASC]
) {
dimensions {
date
}
sum {
cachedBytes
bytes
cachedRequests
requests
pageViews
}
uniq {
uniques
}
}
}
${accountQuery}
}
}
`;
try {
const response = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({ query })
});
const result = (await response.json()) as any;
const trafficData = result?.data?.viewer?.zones?.[0]?.httpRequests1dGroups || [];
const accountData = result?.data?.viewer?.accounts?.[0];
return {
traffic: trafficData,
d1: accountData?.d1AnalyticsAdaptiveGroups || [],
r2: accountData?.r2StorageAdaptiveGroups || [],
missingEnvVars: !accountId || !d1Id || !r2Bucket
};
} catch (err) {
console.error('Failed to fetch analytics', err);
return null;
}
}