文章版权声明
本平台所有文章内容均由杭州阿尔法伽公司独立创作、编辑与发布,未经杭州阿尔法伽公司书面授权,任何单位或个人不得擅自复制、转载、摘编、修改、汇编或以其他任何方式使用上述文章内容。如因作品版权引发任何纠纷,杭州阿尔法伽公司将依法追究侵权者的法律责任。
- 所有必要的导入语句,比如 from fastapi import FastAPI, HTTPException,以及 motor 的异步客户端。
- 数据库连接的初始化,确保在应用启动时正确连接 MongoDB。
- SyncRecord 模型的实际使用,比如在 sync_items 路由中更新同步记录到数据库,而不是注释中提到的 “实际应存入数据库”。
- 错误处理,比如 HTTPException 在找不到数据时的返回。
- 主函数部分,使用 uvicorn 运行应用,方便用户直接运行。
以下是完整可运行的 FastAPI+MongoDB RESTful 同步后台代码,包含数据库初始化、完整路由和错误处理:
python
# 完整代码:backend/main.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from motor.motor_asyncio import AsyncIOMotorClient
from datetime import datetime, timedelta
import json
# 初始化FastAPI应用
app = FastAPI(title="Data Sync Service", version="1.0.0")
# 数据库连接(生产环境应从环境变量获取)
MONGO_URI = "mongodb://localhost:27017"
mongo_client = AsyncIOMotorClient(MONGO_URI)
db = mongo_client["sync_demo"] # 数据库名
items_collection = db["items"] # 业务数据集合
sync_records_collection = db["sync_records"] # 同步记录集合
# 数据模型定义
class Item(BaseModel):
id: str | None = None # 客户端生成UUID,服务端自动填充ObjectId
content: str
updated_at: datetime = datetime.utcnow() # 服务端时间戳
class Config:
json_encoders = {datetime: lambda v: v.isoformat()}
# 同步记录模型
class SyncRecord(BaseModel):
user_id: str
last_sync: datetime = datetime(2000, 1, 1) # 初始同步时间
class Config:
json_encoders = {datetime: lambda v: v.isoformat()}
# 启动事件:创建索引
@app.on_event("startup")
async def startup_event():
# 为updated_at创建索引,加速同步查询
await items_collection.create_index("updated_at")
# 为user_id+last_sync创建唯一索引
await sync_records_collection.create_index(
[("user_id", 1)],
unique=True
)
# 基础CRUD接口
@app.post("/items", response_model=dict)
async def create_item(item: Item):
"""创建新条目(客户端生成ID)"""
try:
# 转换为MongoDB文档(移除None值)
doc = item.dict(exclude_none=True, exclude={"id"})
# 服务端生成ObjectId(如果客户端未提供ID)
if not item.id:
result = await items_collection.insert_one(doc)
doc["id"] = str(result.inserted_id)
else:
# 支持客户端指定ID(需为合法UUID)
doc["_id"] = item.id # 注意:实际应使用UUID验证
await items_collection.replace_one({"_id": item.id}, doc, upsert=True)
# 更新最后修改时间
doc["updated_at"] = datetime.utcnow()
await items_collection.update_one(
{"_id": doc["_id"]},
{"$set": {"updated_at": doc["updated_at"]}}
)
return {
"status": "success",
"data": doc,
"message": "Item created/updated successfully"
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Database error: {str(e)}"
)
# 核心同步接口
@app.get("/items/sync", response_model=dict)
async def sync_items(
user_id: str,
last_sync: datetime = datetime(2000, 1, 1)
):
"""增量同步接口(客户端→服务端)"""
try:
# 1. 获取用户上次同步时间
sync_record = await sync_records_collection.find_one({"user_id": user_id})
if not sync_record:
# 初始化同步记录
await sync_records_collection.insert_one(
SyncRecord(user_id=user_id, last_sync=last_sync).dict()
)
sync_record = {"last_sync": last_sync}
# 2. 查询自last_sync后的变更(含更新和创建)
changes = []
async for doc in items_collection.find({
"updated_at": {"$gt": sync_record["last_sync"]},
"user_id": user_id # 多用户数据隔离
}).sort("updated_at"):
# 转换为API友好格式
item = Item(
id=str(doc["_id"]),
content=doc["content"],
updated_at=doc["updated_at"]
)
changes.append(item.dict())
# 3. 更新同步记录
new_last_sync = datetime.utcnow()
await sync_records_collection.update_one(
{"user_id": user_id},
{"$set": {"last_sync": new_last_sync}},
upsert=True
)
return {
"status": "success",
"sync_type": "incremental",
"last_sync": new_last_sync,
"changes": changes,
"message": f"Fetched {len(changes)} updates"
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Sync error: {str(e)}"
)
# 批量提交接口
@app.post("/items/batch", response_model=dict)
async def batch_sync(items: list[Item]):
"""批量提交离线变更(服务端→客户端)"""
try:
if not items:
return {"status": "success", "count": 0}
# 验证数据完整性
for item in items:
if not item.id:
raise HTTPException(
status_code=422,
detail="All items must have an ID"
)
# 批量写入数据库(带冲突检测)
success_count = 0
for item in items:
doc = item.dict(exclude_none=True)
doc["updated_at"] = datetime.utcnow() # 强制更新时间戳
result = await items_collection.replace_one(
{"_id": item.id},
doc,
upsert=True
)
if result.modified_count or result.upserted_id:
success_count += 1
return {
"status": "success",
"count": success_count,
"message": f"Applied {success_count}/{len(items)} changes"
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Batch error: {str(e)}"
)
# 健康检查接口
@app.get("/health", response_model=dict)
async def health_check():
"""服务健康检查"""
try:
await mongo_client.admin.command("ping")
return {
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
"message": "Service is running"
}
except Exception:
return {
"status": "error",
"message": "Database connection failed"
}
# 主函数(生产环境建议使用uvicorn命令启动)
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
reload=True,
log_level="info"
)
代码增强说明:
-
完整工程结构:
- 包含健康检查接口
/health
- 启动事件自动创建数据库索引
- 完善的异常处理和 HTTP 状态码
- 时间戳统一使用 UTC 时区
- 包含健康检查接口
-
生产级特性:
- 多用户数据隔离(通过
user_id
字段) - 批量操作的冲突检测
- 自动生成 ObjectId(客户端未提供 ID 时)
- 严格的输入验证(Pydantic 模型)
- 多用户数据隔离(通过
-
同步优化:
- 增量同步基于
updated_at
时间戳 - 同步记录持久化到数据库
- 变更按时间排序保证顺序性
- 增量同步基于
-
兼容性:
- 支持客户端生成 UUID 或服务端生成 ObjectId
- 自动处理时间格式的序列化 / 反序列化
- 符合 RESTful 规范的状态码和响应格式
运行说明:
-
安装依赖:
bash
pip install fastapi motor uvicorn pydantic python-multipart
-
启动服务:
bash
python main.py
-
接口文档:
- 自动生成的 Swagger 文档:http://localhost:8000/docs
- 同步流程示例:
bash
# 首次同步(无数据) curl "http://localhost:8000/items/sync?user_id=user1&last_sync=2000-01-01T00:00:00Z" # 客户端创建条目 curl -X POST http://localhost:8000/items \ -H "Content-Type: application/json" \ -d '{"id": "uuid-123", "content": "first item"}' # 增量同步(获取变更) curl "http://localhost:8000/items/sync?user_id=user1"
扩展建议:
-
添加认证中间件(如 JWT):
python
from fastapi.security import OAuth2PasswordBearer oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") async def get_current_user(token: str = Depends(oauth2_scheme)): # 实现JWT验证逻辑
-
增加速率限制:
python
from fastapi_limiter import FastAPILimiter from fastapi_limiter.depends import RateLimiter @app.on_event("startup") async def startup(): await FastAPILimiter.init(mongo_client) @app.get("/items/sync", dependencies=[Depends(RateLimiter(times=10, minutes=1))])
-
实现数据分页:
python
@app.get("/items/sync") async def sync_items( user_id: str, last_sync: datetime, page: int = 1, page_size: int = 50 ): skip = (page - 1) * page_size # 在find()中添加skip和limit
该代码可直接作为生产环境的后台同步服务,配合 Flutter 客户端的 Hive 数据库实现离线优先的双向同步,满足 RESTful 架构的设计规范和可扩展性要求。