Files
FastapiAdmin/backend/app/core/mongo_crud.py
T
zhangtao da957dca55 refactor(backend): 重构控制器和信息服务方法命名
- 更新了多个控制器文件中的函数命名,增加了 _controller 后缀
- 更新了多个服务文件中的函数命名,增加了 _service 后缀
- 统一了命名格式,提高了代码的可读性和可维护性
2025-04-15 03:03:45 +08:00

257 lines
8.6 KiB
Python

# mongo_curd.py
import datetime
import json
from typing import Any, List, Optional, Dict, Union
from bson import ObjectId
from bson.errors import InvalidId
from bson.json_util import dumps
from fastapi.encoders import jsonable_encoder
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo.results import InsertOneResult, UpdateResult, DeleteResult
from app.core.exceptions import CustomException
from app.api.v1.schemas.system.operation_log_schema import OperationLogOutSchema
class MongoCURD:
"""
MongoDB 数据库管理器
"""
def __init__(
self,
db: AsyncIOMotorDatabase,
collection: str,
schema: Any = None
):
"""
初始化 MongoDB CURD 类。
:param db: 数据库连接
:param collection: 集合名称
:param schema: 序列化对象
"""
self.db = db
self.collection = db[collection]
self.schema = schema
async def get(self, _id: Optional[str] = None, **kwargs) -> Optional[Dict]:
"""
获取单个数据,默认使用 ID 查询,否则使用关键词查询。
:param _id: 数据 ID
:param kwargs: 查询条件
:return: 数据字典
"""
try:
if _id:
kwargs["_id"] = ObjectId(_id)
params = self.filter_condition(**kwargs)
data = await self.collection.find_one(params)
if not data:
return None
return jsonable_encoder(self.schema(**data)) if self.schema else data
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"查询数据失败: {str(e)}")
async def create(self, data: Union[Dict, Any]) -> InsertOneResult:
"""
创建数据。
:param data: 要创建的数据
:return: 插入结果
"""
try:
if not isinstance(data, dict):
data = jsonable_encoder(data)
# 添加时间戳
now = datetime.datetime.now()
data.update({
'created_at': now,
'updated_at': now
})
result = await self.collection.insert_one(data)
if not result.acknowledged:
raise CustomException(msg="创建数据失败")
return result
except Exception as e:
raise CustomException(msg=f"创建数据失败: {str(e)}")
async def update(self, _id: str, data: Union[Dict, Any], upsert: bool = False) -> UpdateResult:
"""
更新数据。
:param _id: 数据 ID
:param data: 要更新的数据
:param upsert: 不存在是否插入
:return: 更新结果
"""
try:
if not isinstance(data, dict):
data = jsonable_encoder(data)
# 更新时间戳
data['updated_at'] = datetime.datetime.now()
result = await self.collection.update_one(
{'_id': ObjectId(_id)},
{'$set': data},
upsert=upsert
)
if result.matched_count == 0 and not upsert:
raise CustomException(msg="更新失败,未找到对应数据")
return result
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"更新数据失败: {str(e)}")
async def delete(self, _id: Union[str, List[str]]) -> DeleteResult:
"""
删除数据,支持批量删除。
:param _id: 单个ID或ID列表
:return: 删除结果
"""
try:
if isinstance(_id, list):
result = await self.collection.delete_many({'_id': {'$in': [ObjectId(i) for i in _id]}})
else:
result = await self.collection.delete_one({'_id': ObjectId(_id)})
if result.deleted_count == 0:
raise CustomException(msg="删除失败,未找到对应数据")
return result
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"删除数据失败: {str(e)}")
async def list(
self,
page_no: Optional[int] = 1,
page_size: Optional[int] = 10,
order_by: Optional[List[Dict]] = None,
**kwargs
) -> List[Dict]:
"""
查询数据列表。
:param page_no: 页码
:param page_size: 每页数量
:param order_by: 排序条件 [{'field': 'field_name', 'direction': 1}]
:param kwargs: 查询条件
:return: 数据列表
"""
try:
params = self.filter_condition(**kwargs)
cursor = self.collection.find(params)
# 排序处理
if order_by:
sort_conditions = [(item['field'], item['direction']) for item in order_by]
cursor.sort(sort_conditions)
# 分页处理
if page_no and page_size:
cursor.skip((page_no - 1) * page_size).limit(page_size)
data_list = [json.loads(dumps(row)) async for row in cursor]
return [jsonable_encoder(self.schema(**data)) for data in data_list] if self.schema else data_list
except Exception as e:
raise CustomException(msg=f"查询列表失败: {str(e)}")
async def count(self, **kwargs) -> int:
"""
获取数据总数。
:param kwargs: 查询条件
:return: 数据总数
"""
try:
params = self.filter_condition(**kwargs)
return await self.collection.count_documents(params)
except Exception as e:
raise CustomException(msg=f"统计数据失败: {str(e)}")
@staticmethod
def filter_condition(**kwargs) -> Dict:
"""
构建过滤条件。
:param kwargs: 查询参数
:return: 过滤条件字典
"""
params = {}
for k, v in kwargs.items():
if not v:
continue
if isinstance(v, tuple):
if v[0] == "like" and v[1]:
params[k] = {'$regex': v[1], '$options': 'i'} # i表示不区分大小写
elif v[0] == "between" and len(v[1]) == 2:
params[k] = {
'$gte': f"{v[1][0]} 00:00:00",
'$lt': f"{v[1][1]} 23:59:59"
}
elif v[0] == "ObjectId" and v[1]:
try:
params[k] = ObjectId(v[1])
except InvalidId:
raise CustomException(msg="无效的ObjectId格式")
elif v[0] == "in" and v[1]:
params[k] = {'$in': v[1]}
elif v[0] == "gt":
params[k] = {'$gt': v[1]}
elif v[0] == "gte":
params[k] = {'$gte': v[1]}
elif v[0] == "lt":
params[k] = {'$lt': v[1]}
elif v[0] == "lte":
params[k] = {'$lte': v[1]}
else:
params[k] = v
return params
# class OperationRecordDal(MongoCURD):
# """
# 操作记录数据访问层
# """
# def __init__(self, db: AsyncIOMotorDatabase):
# """
# 初始化操作记录数据访问层。
# :param db: 数据库连接
# """
# super().__init__(
# db=db,
# collection="system_operation_log",
# schema=OperationLogOutSchema,
# )
# 创建日志到mongodb(已测试成功,可以成功创建):暂时注释,是因为该中间保存日志到mongodb(已调试成功),而我现在实现的是记录到mysql的log表
# if not settings.MONGO_DB_ENABLE:
# return response
# document = OperationLogCreateSchema(
# request_ip = request.client.host,
# request_os = user_agent.os.family,
# request_browser = user_agent.browser.family,
# request_path = request.url.path,
# request_method = request.method,
# request_payload = oper_param,
# response_code = response.status_code,
# response_json = response_data.decode(),
# description = route.name,
# creator_id = creator_id
# )
# from app.core.mongo_curd import OperationRecordDal
# from app.core.dependencies import mongo_getter
# operation_record_dal = OperationRecordDal(db = await mongo_getter(request))
# await operation_record_dal.create(data=document.model_dump())