Discord.py+Pymongo:跨服务器用户角色存储及报错解决
问题分析
- 存在性判断逻辑失效:
cursor.find(...)返回的是MongoDB游标对象,哪怕没有匹配数据也永远不会等于None,导致你的判断逻辑完全错误,本该执行更新的场景却触发了插入操作。 - 全局唯一索引冲突:你用用户ID作为文档
_id字段值,而MongoDB的_id是全局唯一的,同一用户在不同服务器执行插入时会触发重复键错误,也就是你收到的“用户已存在”提示。 - 服务器维度未正确隔离:需要确保每个文档对应一个用户在单个服务器的角色数据,唯一标识必须结合服务器ID和用户ID。
解决方案
方案1:复合唯一索引+Upsert简化操作(推荐)
先给MongoDB集合创建guild_id和_id的复合唯一索引(只需在Bot启动时执行一次):
# 初始化集合时执行 cursor.create_index([("guild_id", 1), ("_id", 1)], unique=True)
然后用update_one配合upsert=True,一步完成“存在则更新,不存在则插入”的逻辑:
# 获取当前服务器、用户信息 guild_id = self.ctx.guild.id user_id = self.ctx.author.id user_name = self.ctx.author.name # 查询条件:服务器ID + 用户ID,确保按服务器维度隔离 query = {"guild_id": guild_id, "_id": user_id} # 要更新/插入的数据 update_content = { "$set": { "name": user_name, "roles": [str(r.id) for r in self.ctx.author.roles[1:]] } } # upsert=True:匹配到则更新,未匹配到则插入新文档 cursor.update_one(query, update_content, upsert=True)
方案2:组合ID作为文档唯一标识
如果不想创建复合索引,可以把文档_id设为服务器ID+用户ID的组合字符串,确保每个服务器的用户数据都是独立文档:
guild_id = self.ctx.guild.id user_id = self.ctx.author.id # 生成唯一文档ID doc_unique_id = f"{guild_id}_{user_id}" user_name = self.ctx.author.name query = {"_id": doc_unique_id} update_content = { "$set": { "guild_id": guild_id, "user_id": user_id, "name": user_name, "roles": [str(r.id) for r in self.ctx.author.roles[1:]] } } cursor.update_one(query, update_content, upsert=True)
关键说明
- 获取命令所属服务器:直接调用
self.ctx.guild.id即可,ctx.guild就是命令执行所在的服务器对象。 self.ctx.author.roles[1:]跳过了@everyone角色,这个逻辑如果符合你的需求可以保留,若需要包含所有角色则去掉[1:]。
内容的提问来源于stack exchange,提问作者Dr. Troll
相关产品推荐
相关产品推荐

