从 FireBase 迁移
本文档帮助您将项目从 FireBase 迁移到云开发 CloudBase。
迁移概览
功能对照
| FireBase | CloudBase | 说明 |
|---|---|---|
| Firestore | 文档型数据库 | 都是 NoSQL 文档存储 |
| Cloud Functions | 云函数 | 无服务器函数服务 |
| Cloud Run | 镜像 http 云函数 | 镜像服务托管 |
| Cloud Storage | 云存储 | 文件存储服务 |
| Authentication | 身份认证 | 用户认证服务 |
| Realtime Database | 实时数据推送 | 实时数据同步 |
数据迁移
导出 FireBase 数据
FireBase DataStorage DataBase 数据导出
由于FireBase官方到导出功能只能导出Google Cloud Cloud Datastore 的 BigQuery 格式。所以建议通过脚本导出标准的JSON格式。
首先需要在在 FireBase 控制台 服务账户界面中创建FireBase Admin SDK 的密钥,并下载下来。
然后 clone https://github.com/m417z/node-firestore-import-export/tree/fork 分支进入到仓库目录后 执行 npm install && npm run build 触发构建。构建完成后执行
GOOGLE_APPLICATION_CREDENTIALS=<密钥路径> npm run export -- -b firebase-export.json
就能成功将数据库导出到firebase-export.json 中。
{
"__collections__": {
"albums": {
"HVskzxL7SbQ4CrMV2KoO": {
"description": "A new collection of memories.",
"ownerId": "6TZnxEYS5JVooGWUXf4fvyKBbA33",
"name": "B",
"coverImageUrl": "https://firebasestorage.googleapis.com/...",
"__collections__": {
"photos": {
"3BTRlw1rVSQjfMJzzZ1S": {
"albumId": "HVskzxL7SbQ4CrMV2KoO",
"uploaderId": "6TZnxEYS5JVooGWUXf4fvyKBbA33",
"url": "https://firebasestorage.googleapis.com/...",
"title": "logo.png",
"createdAt": {
"__datatype__": "timestamp",
"value": {
"_seconds": 1770606013,
"_nanoseconds": 801000000
}
},
"location": {
"__datatype__": "geopoint",
"value": {
"_latitude": 32,
"_longitude": 120
}
},
"__collections__": {}
}
}
}
}
}
}
}
其中:
- 顶层
__collections__下是各个集合(如albums) - 集合下的键是文档 ID(如
HVskzxL7SbQ4CrMV2KoO) - 文档中的
__collections__表示该文档下的子集合(如photos) timestamp和geopoint等特殊类型会被编码成带__datatype__和value的对象,后续在转换脚本中需要还原为 CloudBase 支持的时间戳和 GeoJSON 格式。
FireBase Authentication 导出
FireBase CLI 具有批量导出用户的功能。 首先安装
npm i -g firebase-cli
登录FireBase
firebase login
查看 project id
firebase projects:list
最后导出成json
firebase auth:export user.json --project <上一步骤中获取的project id>
firebase auth:export 导出的 user.json 是一个包含 users 数组的 JSON 文件,每个元素是一条用户记录,结构大致如下(示例裁剪自实际导出文件,已做脱敏处理):
{
"users": [
{
"localId": "7hQ2mXbK9vP4sT1cUy3Zr8NaLd0w",
"email": "user@example.com",
"emailVerified": false,
"passwordHash": "aS9xK0lmT3p5VnF4c2hDZ0JhR1p5eGd2bE9YUm1qU3hMUG9uRkE9PQ==",
"salt": "c2FsdF9leGFtcGxlX2Jhc2U2NA==",
"createdAt": "1700000000000",
"lastSignedInAt": "1700003600000",
"providerUserInfo": []
}
]
}
数据格式转换
FireBase 和 CloudBase 的数据格式有差异,需要进行转换。
字段转换对照表
| FireBase 字段/类型 | CloudBase 字段/格式 | 说明 |
|---|---|---|
| 文档 ID(对象键) | _id | FireBase 文档 ID 作为对象的键,需提取为 _id 字段 |
__collections__ | (删除) | 子集合标记,需单独处理子集合数据 |
{"__datatype__": "timestamp", "value": {"_seconds": ..., "_nanoseconds": ...}} | 毫秒时间戳 | 将 _seconds * 1000 + _nanoseconds / 1000000 转换为毫秒时间戳 |
{"__datatype__": "geopoint", "value": {"_latitude": ..., "_longitude": ...}} | {type: "Point", coordinates: [经度, 纬度]} | 转换为 GeoJSON 格式,注意坐标顺序为 [经度, 纬度] |
createdAt(Timestamp 类型) | _createTime | 如果存在,转换为毫秒时间戳 |
updatedAt(Timestamp 类型) | _updateTime | 如果存在,转换为毫秒时间戳 |
userId / ownerId / uploaderId | _openid | 用户相关字段,映射为云开发用户 ID |
| 其他普通字段 | (完整保留) | 字符串、数字、布尔值、数组、对象等业务字段 |
转换示例
FireBase 原始数据(从导出文件中提取的单个文档):
{
"albumId": "HVskzxL7SbQ4CrMV2KoO",
"uploaderId": "6TZnxEYS5JVooGWUXf4fvyKBbA33",
"title": "logo.png",
"createdAt": {
"__datatype__": "timestamp",
"value": {
"_seconds": 1770606013,
"_nanoseconds": 801000000
}
},
"location": {
"__datatype__": "geopoint",
"value": {
"_latitude": 32,
"_longitude": 120
}
}
}
CloudBase 转换后(文档 ID 3BTRlw1rVSQjfMJzzZ1S 作为 _id):
{
"_id": "3BTRlw1rVSQjfMJzzZ1S",
"albumId": "HVskzxL7SbQ4CrMV2KoO",
"_openid": "6TZnxEYS5JVooGWUXf4fvyKBbA33",
"uploaderId": "6TZnxEYS5JVooGWUXf4fvyKBbA33",
"title": "logo.png",
"_createTime": 1770606013801,
"location": {
"type": "Point",
"coordinates": [120, 32]
}
}
转换说明:
- 文档 ID(
3BTRlw1rVSQjfMJzzZ1S)提取为_id字段 createdAt的 Timestamp 转换为毫秒时间戳:1770606013 * 1000 + 801000000 / 1000000 = 1770606013801location的 GeoPoint 转换为 GeoJSON:[经度, 纬度]=[120, 32]uploaderId同时保留原字段并映射到_openid(根据业务需求选择)
导入到 CloudBase
在控制台操作:
- 登录 云开发控制台
- 进入「文档型数据库」→「数据管理」
- 创建对应的集合
- 点击「导入」上传转换后的 JSON 文件
控制台导入限制最大 50MB。如果数据文件超过此限制,请使用批量写入脚本。 :::
可以参考数据转换脚本示例,请根据项目业务逻辑实际使用的字段名编写脚本生成导入所需的JSON文件。
配置安全规则
由于 CloudBase 使用安全规则替代 FireBase 的安全规则,导入数据后需要配置安全规则:
示例 1:公开读,仅创建者可写
{
"read": true,
"write": "doc._openid == auth.openid"
}
更多安全规则配置请参考 安全规则文档。
云函数迁移
FireBase Cloud Functions 可以迁移到 CloudBase 云函数。
简单示例
FireBase Cloud Function:
// FireBase
const functions = require('firebase-functions');
exports.hello = functions.https.onRequest((req, res) => {
const { name } = req.body;
res.json({ message: `Hello, ${name}!` });
});
CloudBase 云函数:
// CloudBase 云函数
exports.main = async (event, context) => {
const { name } = event;
return { message: `Hello, ${name}!` };
};
环境变量迁移
在云函数控制台配置环境变量,替换原有的 FireBase 环境变量:
| FireBase 环境变量 | CloudBase 替代 | 说明 |
|---|---|---|
FIREBASE_PROJECT_ID | ENV_ID | CloudBase 环境 ID |
FIREBASE_CONFIG | 不需要 | 云函数内部自动鉴权 |
文件存储迁移
迁移流程
- 下载文件:批量下载 FireBase Storage 中的文件
可以通过 gsutil打包下载 例如:
gsutil -m cp -r "gs://<project prefix>.firebasestorage.app/<subpath>" .
- 上传文件:将文件上传到腾讯云开发云存储,可以通过COS Browser连接对应COS地址批量上传
- 更新引用:如有必要,应该更新数据库中的文件引用路径
容器镜像托管迁移
FireBase 支持 CloudRun 托管容器镜像,可以通过 HTTP 云函数替代。
详细文档请见:云函数部署镜像
部署完成后可通过配置HTTP 访问服务后访问到。 示例配置文件如下:
{
"envId": "{{envId}}",
"functions": [
{
"name": "helloworld",
"type": "HTTP",
"imageConfig": {
"imageType": "personal",
"imageUri": "ccr.ccs.tencentyun.com/cloudbase/hono-deno-helloworld:amd64"
},
"envVariables": {
"PORT": "9000"
}
}
]
}
需要注意镜像需要是linux/amd64架构,并且监听 9000 端口
SDK 对照表
以下是 FireBase SDK 与 CloudBase SDK 的 API 对照表,帮助您快速完成代码迁移。
初始化
| 操作 | FireBase | CloudBase |
|---|---|---|
| 初始化 SDK | firebase.initializeApp(config) | cloudbase.init({ env }) |
FireBase:
import { initializeApp } from 'firebase/app';
const app = initializeApp({
apiKey: 'your-api-key',
authDomain: 'your-project.firebaseapp.com',
projectId: 'your-project-id',
});
CloudBase:
import cloudbase from '@cloudbase/js-sdk';
const app = cloudbase.init({ env: 'your-env-id' });
数据库操作
| 操作 | FireBase | CloudBase |
|---|---|---|
| 获取数据库引用 | getFirestore(app) | app.database() |
| 获取集合引用 | collection(db, 'collectionName') | db.collection('collectionName') |
| 查询全部 | getDocs(collectionRef) | collection.get() |
| 查询单条 | getDoc(docRef) | collection.doc(id).get() |
| 条件查询 | where('field', '==', value) | collection.where({ field: value }) |
| 大于 | where('field', '>', value) | collection.where({ field: _.gt(value) }) |
| 小于 | where('field', '<', value) | collection.where({ field: _.lt(value) }) |
| 排序 | orderBy('field', 'asc') | collection.orderBy('field', 'asc') |
| 限制数量 | limit(10) | collection.limit(10) |
| 新增数据 | addDoc(collectionRef, data) | collection.add(data) |
| 更新数据 | updateDoc(docRef, { field: value }) | collection.doc(id).update({ field: value }) |
| 删除数据 | deleteDoc(docRef) | collection.doc(id).remove() |
查询示例对比:
// FireBase
import { collection, query, where, orderBy, limit, getDocs } from 'firebase/firestore';
const q = query(
collection(db, 'Todo'),
where('status', '==', 'pending'),
where('priority', '>', 5),
orderBy('createdAt', 'asc'),
limit(20)
);
const results = await getDocs(q);
// CloudBase
const db = app.database();
const _ = db.command;
const results = await db.collection('Todo')
.where({
status: 'pending',
priority: _.gt(5)
})
.orderBy('createdAt', 'asc')
.limit(20)
.get();
云存储操作
| 操作 | FireBase | CloudBase |
|---|---|---|
| 上传文件 | uploadBytes(ref, file) | app.uploadFile({ cloudPath, filePath }) |
| 获取文件 URL | getDownloadURL(ref) | app.getTempFileURL({ fileList }) |
| 删除文件 | deleteObject(ref) | app.deleteFile({ fileList }) |
| 下载文件 | getBytes(ref) | app.downloadFile({ fileID }) |
上传文件示例对比:
// FireBase
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
const storageRef = ref(storage, 'avatars/avatar.png');
await uploadBytes(storageRef, file);
const url = await getDownloadURL(storageRef);
// CloudBase
const result = await app.uploadFile({
cloudPath: 'avatars/avatar.png',
filePath: file,
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);
console.log(`上传进度: ${percent}%`);
}
});
const fileID = result.fileID;
云函数操作
| 操作 | FireBase | CloudBase |
|---|---|---|
| 调用云函数 | httpsCallable(functions, 'functionName') | app.callFunction({ name, data }) |
| 定义云函数 | functions.https.onRequest() | exports.main = async (event, context) => {} |
| 获取调用者信息 | context.auth | context.auth / event.userInfo |
调用云函数示例对比:
// FireBase 客户端
import { getFunctions, httpsCallable } from 'firebase/functions';
const functions = getFunctions(app);
const hello = httpsCallable(functions, 'hello');
const result = await hello({ name: 'World' });
// CloudBase 客户端
const result = await app.callFunction({
name: 'hello',
data: { name: 'World' }
});
console.log(result.result);
身份认证操作
| 操作 | FireBase | CloudBase |
|---|---|---|
| 获取 Auth 对象 | getAuth(app) | app.auth() |
| 邮箱密码注册 | createUserWithEmailAndPassword(auth, email, password) | auth.signUp({ email, password }) |
| 邮箱密码登录 | signInWithEmailAndPassword(auth, email, password) | auth.signInWithEmailAndPassword(email, password) |
| 手机验证码登录 | signInWithPhoneNumber(auth, phone) | auth.signInWithPhoneCode(phone, code) |
| 匿名登录 | signInAnonymously(auth) | auth.signInAnonymously() |
| 获取当前用户 | auth.currentUser | auth.currentUser |
| 退出登录 | signOut(auth) | auth.signOut() |
登录示例对比:
// FireBase - 邮箱密码登录
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth(app);
const userCredential = await signInWithEmailAndPassword(auth, 'email@example.com', 'password');
console.log('登录成功:', userCredential.user);
// CloudBase - 邮箱密码登录
const auth = app.auth();
const loginState = await auth.signInWithEmailAndPassword('email@example.com', 'password');
console.log('登录成功:', loginState.user);
实时数据库
| 操作 | FireBase | CloudBase |
|---|---|---|
| 实时监听 | onSnapshot(docRef, callback) | collection.watch() |
| 取消监听 | unsubscribe() | watcher.close() |
实时监听示例对比:
// FireBase Firestore
import { onSnapshot } from 'firebase/firestore';
const unsubscribe = onSnapshot(docRef, (snapshot) => {
console.log('数据变化:', snapshot.data());
});
// CloudBase 实时数据推送
const db = app.database();
const watcher = db.collection('messages')
.where({ roomId: 'room1' })
.watch({
onChange: (snapshot) => {
console.log('数据变化:', snapshot.docChanges);
},
onError: (error) => {
console.error('监听错误:', error);
}
});
// 取消监听
watcher.close();
更多 API 参考
常见问题
Q:FireBase 的安全规则如何迁移?
CloudBase 使用类似的安全规则语法,但有一些差异。参考 安全规则文档 进行迁移。
Q:实时通信功能如何替代?
CloudBase 支持实时数据推送,可以使用数据库实时监听功能:
const db = app.database();
db.collection('messages')
.where({ roomId: 'xxx' })
.watch({
onChange: (snapshot) => {
console.log('数据变化:', snapshot.docs);
},
});
Q:迁移过程中如何保证业务连续性?
建议采用渐进式迁移:
- 先部署 CloudBase 环境,进行功能测试
- 使用双写策略,新数据同时写入两个平台
- 完成数据迁移后,逐步切换流量
- 确认稳定后,下线 FireBase 服务