Files
FastapiAdmin/backend/app/core/mongo_crud.py
T
zhangtao c2ca6d19ac refactor: 优化代码注释和文档字符串格式
style: 统一代码风格和格式

docs: 完善函数和方法的文档字符串

refactor(base_model): 移除冗余的表名和表参数生成方法

refactor(constant): 更新返回码注释格式

refactor(router_class): 添加路由处理器的详细文档

refactor(database): 完善数据库连接函数的文档

refactor(security): 添加认证类和方法的详细文档

refactor(validator): 更新验证器函数的文档格式

refactor(serialize): 优化序列化工具类的文档

refactor(response): 完善响应类的文档字符串

refactor(dependencies): 添加依赖函数的详细文档

refactor(initialize): 完善初始化脚本的文档

refactor(plugin): 添加生命周期和中间件注册的文档

refactor(service): 完善服务层方法的文档

refactor(controller): 添加控制器方法的详细文档

refactor(crud): 完善CRUD操作的文档字符串

refactor(schema): 简化模型类并移除冗余字段

refactor(param): 更新查询参数类的注释格式

refactor(template): 优化代码生成模板的格式

refactor(console): 添加控制台输出功能的实现

refactor(util): 完善工具函数的文档字符串
2025-10-18 16:31:28 +08:00

303 lines
10 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
class MongoCURD:
"""
MongoDB 数据库管理器
"""
def __init__(
self,
db: AsyncIOMotorDatabase,
collection: str,
schema: Any = None
):
"""
初始化 MongoDB CURD 类。
参数:
- db (AsyncIOMotorDatabase): 数据库连接。
- collection (str): 集合名称。
- schema (Any | None): 序列化对象,传入模型以进行编码,默认 None。
返回:
- None
"""
self.db = db
self.collection = db[collection]
self.schema = schema
async def get(self, _id: Optional[str] = None, **kwargs) -> Optional[Dict]:
"""
获取单个数据,默认使用 ID 查询,否则使用关键词查询。
参数:
- _id (str | None): 数据 ID,若提供则按 ID 查询。
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- Dict | None: 查询到的数据字典,未找到返回 None。
异常:
- CustomException: 当 ID 无效或查询发生错误时抛出。
"""
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:
"""
创建数据。
参数:
- data (Dict | Any): 要创建的数据,可为字典或可编码对象。
返回:
- InsertOneResult: 插入操作结果。
异常:
- CustomException: 创建失败时抛出。
"""
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:
"""
更新数据。
参数:
- _id (str): 数据 ID。
- data (Dict | Any): 要更新的数据,可为字典或可编码对象。
- upsert (bool): 不存在是否插入,默认 False。
返回:
- UpdateResult: 更新操作结果。
异常:
- CustomException: ID 无效或更新失败时抛出。
"""
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:
"""
删除数据,支持批量删除。
参数:
- _id (str | List[str]): 单个 ID 或 ID 列表。
返回:
- DeleteResult: 删除操作结果。
异常:
- CustomException: ID 无效或未删除任何数据时抛出。
"""
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]:
"""
查询数据列表。
参数:
- page_no (int | None): 页码,默认 1。
- page_size (int | None): 每页数量,默认 10。
- order_by (List[Dict] | None): 排序条件,形如 [{'field': '字段名', 'direction': 1}]。
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- List[Dict]: 数据列表。
异常:
- CustomException: 查询失败时抛出。
"""
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:
"""
获取数据总数。
参数:
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- int: 数据总数。
异常:
- CustomException: 统计失败时抛出。
"""
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:
"""
构建过滤条件。
参数:
- kwargs (Dict[str, Any]): 查询参数,支持 ('like'|'between'|'ObjectId'|'in'|'gt'|'gte'|'lt'|'lte') 等操作。
返回:
- Dict: 过滤条件字典。
异常:
- CustomException: 当 ObjectId 格式无效时抛出。
"""
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
# from app.api.v1.module_system.log.schema import OperationLogOutSchema
# 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())