使用FastAPI更新MongoDB时password字段无法更新的问题
问题:MongoDB更新用户时Password字段不生效,其他字段正常
1. MongoDB文档结构
{ "_id": "8a28d1fc-602b-43ba-a017-105a4fff35b3", "isDeleted": false, "user": { "timestamp": "2023-03-03", "phone": "+012345678912", "age": 25, "gender": "male", "nationality": "smth", "universityMajor": "ENGINEERING", "preferences": { "doesMindHavingPets": true }, "password": "$2b$12$9dRbQF2N5kAM44JBAphrrOWT23d206TIZ.VWMhZ9m.PwqnHEZKNhO" } }
2. FastAPI更新函数代码
async def updateUser (id: str, user: BasicUserUpdate = Body(...)): new_user = user.dict(exclude_none=True) user_preferences = None if "preferences" in new_user: user_preferences = new_user['preferences'] del new_user ['preferences'] if 'password' in new_user: new_user['password'] = get_hashed_password(new_user['password']) print (new_user['password']) if (len(new_user) + len(user_preferences)) >= 1: # type: ignore update_result = await dbConnection.find_one_and_update( {"_id": id, "isDeleted" : False}, [ { "$set": { "user.preferences": { "$mergeObjects": [ "$user.preferences", user_preferences ] } } }, { "$set": { "user": { "$mergeObjects": [ "$user", new_user ] } } } ], return_document = ReturnDocument.AFTER) if update_result is not None: return update_result['user'] else: raise HTTPException(status_code=404, detail=f"User {id} not found, or has been banned")
3. 哈希函数实现
password_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def get_hashed_password(password: str) -> str: return password_context.hash(password)
4. 问题现象
- 所有字段均可正常更新,仅
password字段异常:打印显示new_user中的password已正确哈希,但MongoDB中的password始终保持原值。 - 示例请求体:
{ "highPrivacy" : "true", "password" : "abcd1234", "preferences" : { "doesMindHavingPets" : "true", "isMorningPerson" : "false" } }
- 打印的
new_user内容:
{'password': '$2b$12$7256L8YrH1Yz5/S0GutFM.og1fckAoWUboVqZepMLXZ9KnHk4PwrC', 'highPrivacy': True}
问题原因及修复
原因分析
哈希后的bcrypt密码以$开头,而你使用了MongoDB的聚合式更新(数组形式的update参数)。在$mergeObjects聚合表达式中,MongoDB会将$开头的字符串视为字段引用,而非普通字面量字符串。因此new_user中的哈希密码被错误解析为不存在的字段引用,导致$mergeObjects合并时无法覆盖原password字段。
修复方案
将聚合式的整个user对象合并操作,改为逐个字段设置的普通$set操作,这样MongoDB会将哈希密码当作普通字符串处理,避免解析错误。
修改后的更新函数代码:
async def updateUser(id: str, user: BasicUserUpdate = Body(...)): new_user = user.dict(exclude_none=True) user_preferences = None if "preferences" in new_user: user_preferences = new_user['preferences'] del new_user['preferences'] if 'password' in new_user: new_user['password'] = get_hashed_password(new_user['password']) print(new_user['password']) update_ops = [] # 处理user下的普通字段(包括password) if new_user: update_ops.append({ "$set": {f"user.{key}": value for key, value in new_user.items()} }) # 处理preferences的合并 if user_preferences: update_ops.append({ "$set": { "user.preferences": { "$mergeObjects": ["$user.preferences", user_preferences] } } }) if update_ops: update_result = await dbConnection.find_one_and_update( {"_id": id, "isDeleted": False}, update_ops, return_document=ReturnDocument.AFTER ) if update_result is not None: return update_result['user'] else: raise HTTPException(status_code=404, detail=f"User {id} not found, or has been banned")
关键修改点
- 取消了原有的
$mergeObjects合并整个user对象的操作,改为遍历new_user的键值对,逐个设置user.{key}字段。 - 保留了
user.preferences的$mergeObjects合并逻辑,因为这部分需要合并原有偏好和新偏好,且偏好值不会出现$开头的情况。
这样修改后,哈希密码会被当作普通字符串直接写入MongoDB,不会被错误解析为字段引用,password字段即可正常更新。
内容的提问来源于stack exchange,提问作者Ahmet-Salman
相关产品推荐
相关产品推荐

