做了下尝试,将 Google Cloud 上 Firestore 里的数据导入到 Cloudflare 的 KV 里,主要实现思路是
Firestore 里每个 collection 作为 Cloudflare 里的一个 KV
collection 里的每个文档作为 kv 里的一条数据,key 对应文档的 ID,value 为文档的值
废话少说,直接上代码
const { Firestore } = require('@google-cloud/firestore');
const ProgressBar = require('progress');
// 配置 Firestore
const firestore = new Firestore({
projectId: 'GOOGLE_CLOUD_PROJECT_ID', // 替换为你的 Google Cloud 项目 ID
keyFilename: './service-account.json', // 指向服务账号密钥文件
databaseId: 'DATABASE_ID', // 如果是 (default) 可不写
});
// 配置 Cloudflare
const accountId = 'YOUR_CLOUDFLARE_ACCOUNT_ID'; // 替换为您的 Cloudflare 账户 ID
const apiToken = 'YOUR_API_TOKEN'; // 替换为您的 API Token
// 获取或创建 KV 命名空间
async function getOrCreateNamespace(collectionId) {
const listUrl = `https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces`;
// 获取现有命名空间
const listResponse = await fetch(listUrl, {
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
});
const listData = await listResponse.json();
if (!listResponse.ok) {
throw new Error(`获取命名空间列表失败: ${listResponse.status} ${listResponse.statusText}`);
}
// 检查是否已存在同名命名空间
const existingNamespace = listData.result.find(ns => ns.title === collectionId);
if (existingNamespace) {
console.log(`复用现有命名空间: ${collectionId} (ID: ${existingNamespace.id})`);
return existingNamespace.id;
}
// 创建新命名空间
const createUrl = listUrl;
const createResponse = await fetch(createUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: collectionId }),
});
const createData = await createResponse.json();
if (!createResponse.ok) {
throw new Error(`创建命名空间 ${collectionId} 失败: ${createResponse.status} ${createResponse.statusText}`);
}
console.log(`创建新命名空间: ${collectionId} (ID: ${createData.result.id})`);
return createData.result.id;
}
// 从 Firestore 读取数据并组织为集合
async function fetchFirestoreData() {
const collectionsData = {};
try {
const collections = await firestore.listCollections();
console.log(`发现 ${collections.length} 个集合`);
for (const collection of collections) {
const collectionId = collection.id;
console.log(`正在处理集合: ${collectionId}`);
const snapshot = await firestore.collection(collectionId).get();
collectionsData[collectionId] = {};
snapshot.forEach(doc => {
collectionsData[collectionId][doc.id] = JSON.stringify(doc.data());
});
}
return collectionsData;
} catch (error) {
console.error('读取 Firestore 数据时出错:', error);
throw error;
}
}
// 上传到指定 KV 命名空间
async function uploadToKV(namespaceId, kvData, batchSize = 1000) {
const totalEntries = Object.entries(kvData).length;
console.log(`命名空间 ${namespaceId} 总共有 ${totalEntries} 条数据需要上传`);
const bar = new ProgressBar('上传进度 [:bar] :percent | :current/:total | ETA: :etas', {
total: totalEntries,
width: 40,
complete: '=',
incomplete: ' ',
});
const entries = Object.entries(kvData);
const batches = [];
for (let i = 0; i < entries.length; i += batchSize) {
batches.push(entries.slice(i, i + batchSize));
}
for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
const batch = batches[batchIndex];
const promises = batch.map(async ([key, value]) => {
try {
const response = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${accountId}/storage/kv/namespaces/${namespaceId}/values/${key}`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json',
},
body: value,
}
);
if (!response.ok) {
throw new Error(`上传失败: ${key} - ${response.status} ${response.statusText}`);
}
bar.tick();
} catch (error) {
console.error(`错误在键 ${key}:`, error.message);
}
});
await Promise.all(promises);
console.log(`批次 ${batchIndex + 1}/${batches.length} 完成`);
}
console.log(`命名空间 ${namespaceId} 所有数据上传完成!`);
}
// 主函数
async function main() {
try {
// 从 Firestore 获取数据
const collectionsData = await fetchFirestoreData();
// 为每个集合创建或获取命名空间并上传数据
for (const [collectionId, kvData] of Object.entries(collectionsData)) {
console.log(`\n处理集合: ${collectionId}`);
const namespaceId = await getOrCreateNamespace(collectionId);
await uploadToKV(namespaceId, kvData, 1000);
}
console.log('\n所有集合数据已成功导入 Cloudflare KV!');
} catch (error) {
console.error('处理过程中发生错误:', error);
}
}
// 执行
main();
如果有问题,欢迎留言 :)
> 可在 Twitter/X 上评论该篇文章或在下面留言(需要有 GitHub 账号)