引言:文档数据库的革命

在当今大数据和敏捷开发的时代,传统关系型数据库虽然强大,但在处理非结构化数据、快速迭代和水平扩展方面面临挑战。这就是 NoSQL 数据库诞生的背景,而 MongoDB 作为最流行的文档型数据库,以其灵活的文档模型、强大的查询能力和易扩展性,成为了现代应用开发的重要选择。

MongoDB 使用类似 JSON 的 BSON 格式存储数据,这种文档模型与 Python 的字典结构天然契合,使得 Python 成为与 MongoDB 交互的理想语言。本章将深入探讨如何使用 pymongo 库进行 MongoDB 数据库操作,从基础概念到高级特性,并通过实战项目展示如何构建基于 MongoDB 的现代应用。


第一部分:MongoDB 基础与 pymongo 入门

1.1 MongoDB 核心概念

与关系型数据库不同,MongoDB 使用以下概念:

MongoDB 关系型数据库 说明
数据库(Database) 数据库(Database) 数据容器
集合(Collection) 表(Table) 文档组
文档(Document) 行(Row) 数据记录
字段(Field) 列(Column) 数据属性
嵌入式文档 连接(Join) 嵌套数据结构

1.2 安装与配置

安装 MongoDB:

  • Windows:从 MongoDB 官网下载安装程序
  • macOS:使用 Homebrew:brew install mongodb-community
  • Linux (Ubuntu)
    wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
    echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
    sudo apt-get update
    sudo apt-get install -y mongodb-org
    

安装 pymongo:

pip install pymongo

1.3 连接 MongoDB

import pymongo
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure, OperationFailure

def create_connection():
    """创建MongoDB连接"""
    try:
        # 连接到本地MongoDB实例
        client = MongoClient('mongodb://localhost:27017/')
        
        # 测试连接
        client.admin.command('ping')
        print("成功连接到MongoDB")
        
        return client
    except ConnectionFailure as e:
        print(f"连接MongoDB失败: {e}")
        return None
    except Exception as e:
        print(f"发生未知错误: {e}")
        return None

# 高级连接选项
def create_advanced_connection():
    """创建带配置的MongoDB连接"""
    try:
        client = MongoClient(
            host='localhost',
            port=27017,
            username='your_username',  # 如果启用了认证
            password='your_password',
            authSource='admin',        # 认证数据库
            connectTimeoutMS=30000,    # 连接超时30秒
            socketTimeoutMS=30000,     # 套接字超时30秒
            serverSelectionTimeoutMS=30000,  # 服务器选择超时30秒
            maxPoolSize=50,            # 最大连接池大小
            minPoolSize=10,            # 最小连接池大小
            retryWrites=True           # 启用重试写入
        )
        
        # 测试连接
        client.admin.command('ping')
        print("高级连接创建成功")
        
        return client
    except Exception as e:
        print(f"创建连接时发生错误: {e}")
        return None

# 连接到副本集或分片集群
def connect_to_replica_set():
    """连接到MongoDB副本集"""
    try:
        client = MongoClient(
            'mongodb://host1:27017,host2:27017,host3:27017/',
            replicaSet='myReplicaSet',
            readPreference='secondaryPreferred'  # 优先从 secondary 节点读取
        )
        
        client.admin.command('ping')
        print("成功连接到副本集")
        
        return client
    except Exception as e:
        print(f"连接副本集失败: {e}")
        return None

# 测试连接
client = create_connection()
if client:
    # 获取数据库列表
    print("数据库列表:", client.list_database_names())
    client.close()

第二部分:数据库与集合操作

2.1 数据库操作

def database_operations():
    """数据库操作示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        # 获取或创建数据库
        db = client['company']  # 如果不存在会自动创建
        print(f"使用数据库: {db.name}")
        
        # 获取所有集合名称
        collections = db.list_collection_names()
        print("集合列表:", collections)
        
        # 检查集合是否存在
        if 'employees' in collections:
            print("employees集合已存在")
        else:
            print("employees集合不存在")
        
        # 获取数据库统计信息
        stats = db.command('dbStats')
        print(f"数据库大小: {stats['dataSize']} 字节")
        print(f"文档数量: {stats['objects']}")
        
    except Exception as e:
        print(f"数据库操作时发生错误: {e}")
    finally:
        client.close()

# 执行数据库操作
database_operations()

2.2 集合操作

def collection_operations():
    """集合操作示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        
        # 获取或创建集合
        employees_collection = db['employees']
        print(f"使用集合: {employees_collection.name}")
        
        # 创建带选项的集合
        try:
            # 创建带验证规则的集合
            db.create_collection(
                'departments',
                validator={
                    '$jsonSchema': {
                        'bsonType': 'object',
                        'required': ['name', 'location'],
                        'properties': {
                            'name': {
                                'bsonType': 'string',
                                'description': '必须是一个字符串'
                            },
                            'location': {
                                'bsonType': 'string',
                                'description': '必须是一个字符串'
                            },
                            'budget': {
                                'bsonType': 'double',
                                'description': '必须是一个数字'
                            }
                        }
                    }
                }
            )
            print("带验证规则的departments集合创建成功")
        except Exception as e:
            print(f"创建集合时发生错误: {e}")
        
        # 获取集合统计信息
        stats = db.command('collStats', 'employees')
        print(f"集合大小: {stats['size']} 字节")
        print(f"文档数量: {stats['count']}")
        
        # 重命名集合
        # db['old_collection'].rename('new_collection')
        
        # 删除集合
        # db['temp_collection'].drop()
        
    except Exception as e:
        print(f"集合操作时发生错误: {e}")
    finally:
        client.close()

# 执行集合操作
collection_operations()

第三部分:文档 CRUD 操作

3.1 插入文档(Create)

def insert_documents():
    """插入文档示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        departments = db['departments']
        
        # 插入单个文档
        department_data = {
            'name': '技术部',
            'location': '北京',
            'budget': 1000000.00,
            'created_at': datetime.now()
        }
        
        dept_result = departments.insert_one(department_data)
        print(f"插入部门成功,ID: {dept_result.inserted_id}")
        
        # 插入多个文档
        employees_data = [
            {
                'first_name': '张',
                'last_name': '三',
                'email': 'zhangsan@email.com',
                'phone': '13800138001',
                'hire_date': datetime(2020, 1, 15),
                'salary': 15000.00,
                'department': dept_result.inserted_id,
                'skills': ['Python', 'JavaScript', 'SQL'],
                'address': {
                    'street': '海淀区中关村大街',
                    'city': '北京',
                    'zipcode': '100000'
                },
                'created_at': datetime.now()
            },
            {
                'first_name': '李',
                'last_name': '四',
                'email': 'lisi@email.com',
                'phone': '13800138002',
                'hire_date': datetime(2019, 3, 10),
                'salary': 12000.00,
                'department': dept_result.inserted_id,
                'skills': ['Java', 'Spring', 'MySQL'],
                'address': {
                    'street': '朝阳区国贸',
                    'city': '北京',
                    'zipcode': '100000'
                },
                'created_at': datetime.now()
            }
        ]
        
        emp_result = employees.insert_many(employees_data)
        print(f"插入员工成功,ID列表: {emp_result.inserted_ids}")
        
        # 插入带自定义ID的文档
        custom_id_doc = {
            '_id': 'emp_001',
            'first_name': '王',
            'last_name': '五',
            'email': 'wangwu@email.com',
            'created_at': datetime.now()
        }
        
        custom_result = employees.insert_one(custom_id_doc)
        print(f"插入自定义ID文档成功,ID: {custom_result.inserted_id}")
        
    except Exception as e:
        print(f"插入文档时发生错误: {e}")
    finally:
        client.close()

# 执行插入操作
insert_documents()

3.2 查询文档(Read)

def query_documents():
    """查询文档示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 查询所有文档
        print("所有员工:")
        all_employees = employees.find()
        for emp in all_employees:
            print(f"{emp['first_name']} {emp['last_name']} - {emp['email']}")
        
        # 条件查询
        print("\n薪资大于10000的员工:")
        well_paid_employees = employees.find({'salary': {'$gt': 10000}})
        for emp in well_paid_employees:
            print(f"{emp['first_name']} {emp['last_name']}: ¥{emp['salary']:,.2f}")
        
        # 多条件查询
        print("\n在北京的技术部员工:")
        tech_employees = employees.find({
            'salary': {'$gt': 10000},
            'address.city': '北京'
        })
        for emp in tech_employees:
            print(f"{emp['first_name']} {emp['last_name']}")
        
        # 投影(选择返回的字段)
        print("\n只返回姓名和邮箱:")
        name_email_only = employees.find(
            {},  # 空查询条件表示所有文档
            {'first_name': 1, 'last_name': 1, 'email': 1, '_id': 0}  # 1表示包含,0表示排除
        )
        for emp in name_email_only:
            print(emp)
        
        # 排序和限制
        print("\n薪资最高的3名员工:")
        top_earners = employees.find().sort('salary', -1).limit(3)
        for emp in top_earners:
            print(f"{emp['first_name']} {emp['last_name']}: ¥{emp['salary']:,.2f}")
        
        # 统计文档数量
        count = employees.count_documents({'salary': {'$gt': 10000}})
        print(f"\n薪资大于10000的员工数量: {count}")
        
        # 查询单个文档
        zhangsan = employees.find_one({'first_name': '张', 'last_name': '三'})
        if zhangsan:
            print(f"\n找到张三: {zhangsan['email']}")
        
    except Exception as e:
        print(f"查询文档时发生错误: {e}")
    finally:
        client.close()

# 执行查询操作
query_documents()

3.3 更新文档(Update)

def update_documents():
    """更新文档示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 更新单个文档
        update_result = employees.update_one(
            {'first_name': '张', 'last_name': '三'},
            {'$set': {'salary': 16000.00, 'last_updated': datetime.now()}}
        )
        print(f"匹配文档数: {update_result.matched_count}, 修改文档数: {update_result.modified_count}")
        
        # 更新多个文档
        update_many_result = employees.update_many(
            {'salary': {'$lt': 15000}},
            {'$inc': {'salary': 1000}, '$set': {'last_updated': datetime.now()}}
        )
        print(f"薪资调整影响员工数: {update_many_result.modified_count}")
        
        # 使用更新操作符
        # $set: 设置字段值
        # $unset: 删除字段
        # $inc: 增加字段值
        # $push: 向数组添加元素
        # $pull: 从数组移除元素
        
        # 添加技能到员工
        employees.update_one(
            {'first_name': '张', 'last_name': '三'},
            {'$push': {'skills': 'MongoDB'}}
        )
        print("已添加MongoDB技能")
        
        # 替换整个文档
        replacement = {
            'first_name': '张',
            'last_name': '三',
            'email': 'zhangsan_new@email.com',
            'position': '高级工程师',
            'updated_at': datetime.now()
        }
        
        replace_result = employees.replace_one(
            {'first_name': '张', 'last_name': '三'},
            replacement
        )
        print(f"文档替换结果: 匹配{replace_result.matched_count}个, 修改{replace_result.modified_count}个")
        
    except Exception as e:
        print(f"更新文档时发生错误: {e}")
    finally:
        client.close()

# 执行更新操作
update_documents()

3.4 删除文档(Delete)

def delete_documents():
    """删除文档示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 先插入一些测试数据
        test_employee = {
            'first_name': 'Test',
            'last_name': 'User',
            'email': 'test@example.com',
            'created_at': datetime.now()
        }
        test_result = employees.insert_one(test_employee)
        print(f"插入测试员工成功,ID: {test_result.inserted_id}")
        
        # 删除单个文档
        delete_result = employees.delete_one({'email': 'test@example.com'})
        print(f"删除测试员工,删除文档数: {delete_result.deleted_count}")
        
        # 删除多个文档(谨慎使用!)
        # 先标记一些文档为待删除
        employees.update_many(
            {'salary': {'$lt': 10000}},
            {'$set': {'to_delete': True}}
        )
        
        # 删除标记的文档
        delete_many_result = employees.delete_many({'to_delete': True})
        print(f"删除低薪资员工,删除文档数: {delete_many_result.deleted_count}")
        
        # 更安全的做法:标记删除而不是物理删除
        employees.update_many(
            {'salary': {'$lt': 10000}},
            {'$set': {'active': False, 'inactive_date': datetime.now()}}
        )
        print("已标记低薪资员工为不活跃状态")
        
        # 删除所有文档(非常危险!)
        # delete_all_result = employees.delete_many({})
        # print(f"删除所有文档,删除文档数: {delete_all_result.deleted_count}")
        
    except Exception as e:
        print(f"删除文档时发生错误: {e}")
    finally:
        client.close()

# 执行删除操作
delete_documents()

第四部分:高级查询与聚合操作

4.1 复杂查询操作

def complex_queries():
    """复杂查询示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 数组查询
        print("会Python的员工:")
        python_devs = employees.find({'skills': 'Python'})
        for emp in python_devs:
            print(f"{emp['first_name']} {emp['last_name']}: {emp['skills']}")
        
        # 多技能查询
        print("\n既会Python又会JavaScript的员工:")
        full_stack_devs = employees.find({'skills': {'$all': ['Python', 'JavaScript']}})
        for emp in full_stack_devs:
            print(f"{emp['first_name']} {emp['last_name']}")
        
        # 正则表达式查询
        print("\n邮箱是Gmail的员工:")
        gmail_users = employees.find({'email': {'$regex': '@gmail\\.com$'}})
        for emp in gmail_users:
            print(f"{emp['first_name']} {emp['last_name']}: {emp['email']}")
        
        # 嵌套文档查询
        print("\n在北京的员工:")
        beijing_employees = employees.find({'address.city': '北京'})
        for emp in beijing_employees:
            print(f"{emp['first_name']} {emp['last_name']}: {emp['address']['city']}")
        
        # 范围查询
        print("\n在2020年入职的员工:")
        start_2020 = datetime(2020, 1, 1)
        end_2020 = datetime(2020, 12, 31)
        employees_2020 = employees.find({
            'hire_date': {
                '$gte': start_2020,
                '$lte': end_2020
            }
        })
        for emp in employees_2020:
            print(f"{emp['first_name']} {emp['last_name']}: {emp['hire_date']}")
        
        # 或查询
        print("\n薪资大于15000或在技术部的员工:")
        high_salary_or_tech = employees.find({
            '$or': [
                {'salary': {'$gt': 15000}},
                {'department': {'$exists': True}}  # 假设技术部文档有department字段
            ]
        })
        for emp in high_salary_or_tech:
            print(f"{emp['first_name']} {emp['last_name']}: ¥{emp.get('salary', 0):,.2f}")
        
    except Exception as e:
        print(f"复杂查询时发生错误: {e}")
    finally:
        client.close()

# 执行复杂查询
complex_queries()

4.2 聚合框架

MongoDB 的聚合框架提供了强大的数据处理能力:

def aggregation_examples():
    """聚合操作示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 简单的分组聚合
        print("按城市统计员工数量:")
        pipeline_city = [
            {'$group': {
                '_id': '$address.city',
                'count': {'$sum': 1},
                'avg_salary': {'$avg': '$salary'},
                'max_salary': {'$max': '$salary'},
                'min_salary': {'$min': '$salary'}
            }},
            {'$sort': {'count': -1}}
        ]
        
        city_stats = employees.aggregate(pipeline_city)
        for stat in city_stats:
            print(f"{stat['_id']}: {stat['count']}人, 平均工资: ¥{stat['avg_salary']:,.2f}")
        
        # 多阶段聚合
        print("\n技能统计:")
        pipeline_skills = [
            {'$unwind': '$skills'},  # 将数组拆分为多个文档
            {'$group': {
                '_id': '$skills',
                'count': {'$sum': 1},
                'practitioners': {'$push': '$first_name'}
            }},
            {'$sort': {'count': -1}},
            {'$limit': 5}
        ]
        
        skills_stats = employees.aggregate(pipeline_skills)
        for stat in skills_stats:
            print(f"{stat['_id']}: {stat['count']}人掌握")
        
        # 复杂聚合:按部门统计
        print("\n部门统计:")
        pipeline_department = [
            {'$lookup': {  # 类似SQL的JOIN
                'from': 'departments',
                'localField': 'department',
                'foreignField': '_id',
                'as': 'dept_info'
            }},
            {'$unwind': '$dept_info'},
            {'$group': {
                '_id': '$dept_info.name',
                'employee_count': {'$sum': 1},
                'total_salary': {'$sum': '$salary'},
                'avg_salary': {'$avg': '$salary'},
                'employees': {'$push': {
                    'name': {'$concat': ['$first_name', ' ', '$last_name']},
                    'salary': '$salary'
                }}
            }},
            {'$sort': {'total_salary': -1}}
        ]
        
        dept_stats = employees.aggregate(pipeline_department)
        for stat in dept_stats:
            print(f"{stat['_id']}: {stat['employee_count']}人, 总工资: ¥{stat['total_salary']:,.2f}")
        
        # 使用聚合管道进行数据转换
        print("\n薪资等级分布:")
        pipeline_salary_brackets = [
            {'$bucket': {
                'groupBy': '$salary',
                'boundaries': [0, 10000, 15000, 20000, 30000],
                'default': '30000+',
                'output': {
                    'count': {'$sum': 1},
                    'employees': {'$push': '$first_name'}
                }
            }},
            {'$sort': {'_id': 1}}
        ]
        
        salary_brackets = employees.aggregate(pipeline_salary_brackets)
        for bracket in salary_brackets:
            print(f"薪资范围 {bracket['_id']}: {bracket['count']}人")
        
    except Exception as e:
        print(f"聚合操作时发生错误: {e}")
    finally:
        client.close()

# 执行聚合操作
aggregation_examples()

4.3 索引优化

def index_operations():
    """索引操作示例"""
    client = create_connection()
    if not client:
        return
    
    try:
        db = client['company']
        employees = db['employees']
        
        # 创建单字段索引
        employees.create_index([('email', pymongo.ASCENDING)], unique=True)
        print("已创建email唯一索引")
        
        # 创建复合索引
        employees.create_index([
            ('department', pymongo.ASCENDING),
            ('salary', pymongo.DESCENDING)
        ])
        print("已创建部门-薪资复合索引")
        
        # 创建文本索引(用于全文搜索)
        employees.create_index([
            ('first_name', pymongo.TEXT),
            ('last_name', pymongo.TEXT),
            ('skills', pymongo.TEXT)
        ])
        print("已创建文本索引")
        
        # 查看索引信息
        indexes = employees.list_indexes()
        print("\n集合索引:")
        for index in indexes:
            print(f"索引名称: {index['name']}, 键: {index['key']}")
        
        # 使用文本搜索
        print("\n搜索'Python'相关的员工:")
        text_results = employees.find({
            '$text': {'$search': 'Python'}
        })
        for emp in text_results:
            print(f"{emp['first_name']} {emp['last_name']}")
        
        # 删除索引
        # employees.drop_index('email_1')
        # print("已删除email索引")
        
    except Exception as e:
        print(f"索引操作时发生错误: {e}")
    finally:
        client.close()

# 执行索引操作
index_operations()

第五部分:综合实战项目——博客平台后端

现在让我们构建一个完整的博客平台后端,使用 MongoDB 存储数据。

项目功能:

  • 用户管理
  • 文章发布与管理
  • 评论系统
  • 标签分类
  • 搜索功能

代码实现:

# blog_platform.py
import pymongo
from datetime import datetime, timedelta
from bson import ObjectId
from pymongo import MongoClient, DESCENDING, ASCENDING
from pymongo.errors import DuplicateKeyError, OperationFailure
import re

class BlogPlatform:
    def __init__(self, db_name='blog_db'):
        self.client = MongoClient('mongodb://localhost:27017/')
        self.db = self.client[db_name]
        self.init_database()
    
    def init_database(self):
        """初始化数据库结构和索引"""
        # 创建集合(如果不存在会自动创建)
        self.users = self.db['users']
        self.posts = self.db['posts']
        self.comments = self.db['comments']
        self.categories = self.db['categories']
        
        # 创建索引
        try:
            # 用户集合索引
            self.users.create_index('email', unique=True)
            self.users.create_index('username', unique=True)
            
            # 文章集合索引
            self.posts.create_index('author_id')
            self.posts.create_index('category_id')
            self.posts.create_index('tags')
            self.posts.create_index('created_at')
            self.posts.create_index([
                ('title', 'text'),
                ('content', 'text'),
                ('tags', 'text')
            ], name='search_index')
            
            # 评论集合索引
            self.comments.create_index('post_id')
            self.comments.create_index('author_id')
            self.comments.create_index('created_at')
            
            # 分类集合索引
            self.categories.create_index('name', unique=True)
            
            print("数据库初始化完成")
        except Exception as e:
            print(f"初始化数据库时发生错误: {e}")
    
    def create_user(self, username, email, password_hash, display_name=None):
        """创建用户"""
        try:
            user_data = {
                'username': username,
                'email': email,
                'password_hash': password_hash,
                'display_name': display_name or username,
                'created_at': datetime.now(),
                'last_login': None,
                'is_active': True,
                'role': 'user',  # user, author, admin
                'profile': {
                    'bio': '',
                    'avatar_url': None,
                    'website': None
                }
            }
            
            result = self.users.insert_one(user_data)
            print(f"用户 {username} 创建成功,ID: {result.inserted_id}")
            return result.inserted_id
        except DuplicateKeyError:
            print(f"用户名或邮箱已存在: {username}/{email}")
            return None
        except Exception as e:
            print(f"创建用户时发生错误: {e}")
            return None
    
    def create_post(self, author_id, title, content, category_id=None, tags=None):
        """创建博客文章"""
        try:
            post_data = {
                'title': title,
                'content': content,
                'author_id': author_id,
                'category_id': category_id,
                'tags': tags or [],
                'created_at': datetime.now(),
                'updated_at': datetime.now(),
                'published': True,
                'published_at': datetime.now(),
                'views': 0,
                'likes': 0,
                'comments_count': 0,
                'slug': self.generate_slug(title),
                'meta': {
                    'description': content[:150] + '...' if len(content) > 150 else content,
                    'keywords': tags or []
                }
            }
            
            result = self.posts.insert_one(post_data)
            print(f"文章 '{title}' 创建成功,ID: {result.inserted_id}")
            return result.inserted_id
        except Exception as e:
            print(f"创建文章时发生错误: {e}")
            return None
    
    def generate_slug(self, title):
        """生成URL友好的slug"""
        slug = re.sub(r'[^\w\s-]', '', title.lower())
        slug = re.sub(r'[-\s]+', '-', slug).strip('-')
        return slug
    
    def add_comment(self, post_id, author_id, content, parent_id=None):
        """添加评论"""
        try:
            comment_data = {
                'post_id': post_id,
                'author_id': author_id,
                'content': content,
                'parent_id': parent_id,  # 用于回复评论
                'created_at': datetime.now(),
                'updated_at': datetime.now(),
                'likes': 0,
                'is_approved': True
            }
            
            result = self.comments.insert_one(comment_data)
            
            # 更新文章的评论计数
            self.posts.update_one(
                {'_id': post_id},
                {'$inc': {'comments_count': 1}}
            )
            
            print(f"评论添加成功,ID: {result.inserted_id}")
            return result.inserted_id
        except Exception as e:
            print(f"添加评论时发生错误: {e}")
            return None
    
    def get_recent_posts(self, limit=10, page=1):
        """获取最近的文章"""
        try:
            skip = (page - 1) * limit
            posts = self.posts.find(
                {'published': True},
                {'content': 0}  # 不返回内容,提高性能
            ).sort('created_at', DESCENDING).skip(skip).limit(limit)
            
            # 填充作者信息
            posts_list = []
            for post in posts:
                author = self.users.find_one(
                    {'_id': post['author_id']},
                    {'display_name': 1, 'username': 1}
                )
                post['author'] = author
                posts_list.append(post)
            
            return posts_list
        except Exception as e:
            print(f"获取文章时发生错误: {e}")
            return []
    
    def get_post_by_slug(self, slug):
        """根据slug获取文章详情"""
        try:
            post = self.posts.find_one({'slug': slug})
            if not post:
                return None
            
            # 增加浏览次数
            self.posts.update_one(
                {'_id': post['_id']},
                {'$inc': {'views': 1}}
            )
            
            # 获取作者信息
            author = self.users.find_one(
                {'_id': post['author_id']},
                {'display_name': 1, 'username': 1, 'profile': 1}
            )
            post['author'] = author
            
            # 获取评论
            comments = self.comments.find(
                {'post_id': post['_id'], 'is_approved': True}
            ).sort('created_at', ASCENDING)
            
            # 获取评论作者信息
            comments_list = []
            for comment in comments:
                comment_author = self.users.find_one(
                    {'_id': comment['author_id']},
                    {'display_name': 1, 'username': 1}
                )
                comment['author'] = comment_author
                comments_list.append(comment)
            
            post['comments'] = comments_list
            
            return post
        except Exception as e:
            print(f"获取文章详情时发生错误: {e}")
            return None
    
    def search_posts(self, query, limit=10, page=1):
        """搜索文章"""
        try:
            skip = (page - 1) * limit
            
            # 文本搜索
            results = self.posts.find(
                {
                    '$text': {'$search': query},
                    'published': True
                },
                {
                    'score': {'$meta': 'textScore'},
                    'content': 0
                }
            ).sort([('score', {'$meta': 'textScore'})]).skip(skip).limit(limit)
            
            posts_list = []
            for post in results:
                author = self.users.find_one(
                    {'_id': post['author_id']},
                    {'display_name': 1, 'username': 1}
                )
                post['author'] = author
                posts_list.append(post)
            
            return posts_list
        except Exception as e:
            print(f"搜索文章时发生错误: {e}")
            return []
    
    def get_popular_posts(self, limit=5):
        """获取热门文章"""
        try:
            pipeline = [
                {'$match': {'published': True}},
                {'$sort': {'views': DESCENDING}},
                {'$limit': limit},
                {'$project': {
                    'title': 1,
                    'slug': 1,
                    'views': 1,
                    'created_at': 1,
                    'author_id': 1
                }}
            ]
            
            posts = self.posts.aggregate(pipeline)
            
            posts_list = []
            for post in posts:
                author = self.users.find_one(
                    {'_id': post['author_id']},
                    {'display_name': 1}
                )
                post['author'] = author
                posts_list.append(post)
            
            return posts_list
        except Exception as e:
            print(f"获取热门文章时发生错误: {e}")
            return []
    
    def get_posts_by_tag(self, tag, limit=10, page=1):
        """根据标签获取文章"""
        try:
            skip = (page - 1) * limit
            posts = self.posts.find(
                {
                    'tags': tag,
                    'published': True
                },
                {'content': 0}
            ).sort('created_at', DESCENDING).skip(skip).limit(limit)
            
            posts_list = []
            for post in posts:
                author = self.users.find_one(
                    {'_id': post['author_id']},
                    {'display_name': 1, 'username': 1}
                )
                post['author'] = author
                posts_list.append(post)
            
            return posts_list
        except Exception as e:
            print(f"根据标签获取文章时发生错误: {e}")
            return []
    
    def get_category_stats(self):
        """获取分类统计"""
        try:
            pipeline = [
                {'$match': {'published': True}},
                {'$group': {
                    '_id': '$category_id',
                    'post_count': {'$sum': 1},
                    'total_views': {'$sum': '$views'},
                    'total_comments': {'$sum': '$comments_count'}
                }},
                {'$lookup': {
                    'from': 'categories',
                    'localField': '_id',
                    'foreignField': '_id',
                    'as': 'category_info'
                }},
                {'$unwind': '$category_info'},
                {'$project': {
                    'category_name': '$category_info.name',
                    'post_count': 1,
                    'total_views': 1,
                    'total_comments': 1
                }},
                {'$sort': {'post_count': DESCENDING}}
            ]
            
            stats = self.posts.aggregate(pipeline)
            return list(stats)
        except Exception as e:
            print(f"获取分类统计时发生错误: {e}")
            return []
    
    def generate_sample_data(self):
        """生成示例数据"""
        try:
            # 创建示例用户
            admin_id = self.create_user('admin', 'admin@blog.com', 'hashed_password', '管理员')
            author1_id = self.create_user('author1', 'author1@blog.com', 'hashed_password', '作者一')
            author2_id = self.create_user('author2', 'author2@blog.com', 'hashed_password', '作者二')
            
            # 创建分类
            categories = [
                {'name': '技术', 'description': '技术相关文章'},
                {'name': '生活', 'description': '生活相关文章'},
                {'name': '旅行', 'description': '旅行相关文章'}
            ]
            
            category_ids = []
            for category in categories:
                result = self.categories.insert_one(category)
                category_ids.append(result.inserted_id)
            
            # 创建示例文章
            posts = [
                {
                    'author_id': author1_id,
                    'title': 'Python MongoDB 教程',
                    'content': '这是一篇关于如何使用Python操作MongoDB的详细教程...',
                    'category_id': category_ids[0],
                    'tags': ['Python', 'MongoDB', '数据库']
                },
                {
                    'author_id': author1_id,
                    'title': 'Web开发最佳实践',
                    'content': '本文介绍了现代Web开发的最佳实践和模式...',
                    'category_id': category_ids[0],
                    'tags': ['Web开发', '最佳实践', '编程']
                },
                {
                    'author_id': author2_id,
                    'title': '我的西藏之旅',
                    'content': '分享我在西藏旅行的经历和见闻...',
                    'category_id': category_ids[2],
                    'tags': ['旅行', '西藏', '摄影']
                }
            ]
            
            for post_data in posts:
                self.create_post(**post_data)
            
            print("示例数据生成成功")
            
        except Exception as e:
            print(f"生成示例数据时发生错误: {e}")

# 使用示例
def main():
    # 创建博客平台实例
    blog = BlogPlatform()
    
    # 生成示例数据
    blog.generate_sample_data()
    
    # 获取最近文章
    print("最近文章:")
    recent_posts = blog.get_recent_posts(limit=5)
    for post in recent_posts:
        print(f"- {post['title']} by {post['author']['display_name']}")
    
    # 搜索文章
    print("\n搜索'Python'相关文章:")
    search_results = blog.search_posts('Python')
    for post in search_results:
        print(f"- {post['title']} (相关度: {post.get('score', 0):.2f})")
    
    # 获取热门文章
    print("\n热门文章:")
    popular_posts = blog.get_popular_posts()
    for post in popular_posts:
        print(f"- {post['title']} ({post['views']} 次阅读)")
    
    # 获取分类统计
    print("\n分类统计:")
    category_stats = blog.get_category_stats()
    for stat in category_stats:
        print(f"{stat['category_name']}: {stat['post_count']}篇文章, {stat['total_views']}次阅读")

if __name__ == '__main__':
    main()

项目扩展思路:

  1. 用户认证系统:添加JWT token认证
  2. 文件上传:集成图片和文件上传功能
  3. 缓存系统:使用Redis缓存热门文章和查询结果
  4. API接口:创建RESTful API供前端应用调用
  5. 后台管理:构建文章管理和用户管理后台
  6. SEO优化:添加sitemap和meta标签优化
  7. 社交媒体集成:添加社交分享和评论功能

总结与最佳实践

通过本章的学习,你已经全面掌握了使用pymongo进行MongoDB数据库操作的各个方面:

  1. MongoDB基础:了解了文档数据库的概念和特点
  2. pymongo连接:掌握了连接MongoDB的各种方式和配置选项
  3. CRUD操作:深入理解了文档的增删改查操作
  4. 高级查询:学会了复杂查询、聚合管道和索引优化
  5. 实战项目:构建了完整的博客平台后端,综合运用了所学知识

最佳实践总结:

  • 合理设计文档结构,避免过度嵌套和数组过大
  • 为常用查询字段创建合适的索引
  • 使用投影优化查询性能,只返回需要的字段
  • 合理使用聚合管道进行复杂数据处理
  • 实施适当的错误处理和重试机制
  • 定期监控和优化数据库性能
  • 使用连接池管理数据库连接
  • 实施适当的数据备份和恢复策略

MongoDB作为最流行的NoSQL数据库,与Python的结合为开发现代Web应用、移动应用和大数据处理系统提供了强大的数据存储和处理能力。通过掌握pymongo的使用,你能够构建灵活、可扩展和高性能的数据驱动应用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐