173 lines
4.2 KiB
TypeScript
173 lines
4.2 KiB
TypeScript
/* 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;
|
||
}
|
||
}
|