Frappe 站点创建测试用户流程

TL;DR

在远程 Frappe 站点通过 bench console 创建一个可登录的测试用户。关键难点:分步 commit 避免事务回滚、密码函数参数顺序、角色名必须存在于站点。

现状:✅ 已验证,测试完可一键删除。


🎯 背景:为什么需要这个

  • 需求:SSO 测试需要一个真实可登录的 Frappe 用户来验证 OIDC 登录流程
  • 卡点:不能动老师的管理员密码;Frappe update_password() 的参数顺序和调用方式容易踩坑;gottenergy 迁移站点的角色体系不是标准的 System User
  • 约束:测试完必须能彻底删除用户,不留痕迹

✅ 前置条件

  • 可 SSH 到 Press VPS (104.236.24.124)
  • 通过 press-backend 容器可跳板到 bench 容器
  • 知道目标站点的角色列表(frappe.get_all("Role")

🏗️ 最终架构

本地 → Press VPS → press-backend 容器 → SSH → n1/f1 VPS → bench console

为什么是三层跳板:Press VPS 的 press-backend 容器有 SSH key 可以免密访问 n1/f1。


🛠️ 部署步骤

第 1 步:进入目标站点 console

docker exec -i $(docker ps -qf "name=press-backend") ssh root@45.55.93.224 "echo '' | docker exec -i bench-0003-000007-f1 bench --site gottenergy.getgrowth.app console"

第 2 步:创建用户(分步 commit)

import frappe
from frappe.utils.password import update_password
frappe.init(site="gottenergy.getgrowth.app")
frappe.connect()
 
# 创建用户
user = frappe.get_doc({
    "doctype": "User",
    "email": "test@getgrowth.app",
    "first_name": "Test",
    "send_welcome_email": 0,
    "enabled": 1
})
user.insert(ignore_permissions=True)
frappe.db.commit()  # 先提交,防止后续失败回滚掉用户
 
# 重新加载
user.reload()
user.append("roles", {"role": "Desk User"})
user.save()
frappe.db.commit()  # 单独提交角色
 
# 设密码
update_password("test@getgrowth.app", "test1234")
frappe.db.commit()
 
print("Created: test@getgrowth.app / test1234")

第 3 步:确认密码生效

# 直接登录 https://gottenergy.getgrowth.app 验证

↩️ 回滚 / 卸载

  • 删除用户frappe.delete_doc("User", "test@getgrowth.app") 然后 frappe.db.commit()
  • 验证已删除frappe.db.exists("User", "test@getgrowth.app") 应返回 None
  • Auth 表残留:MariaDB __Auth 表会自动级联删除,不需要手动清理

🔍 验证方法

  1. frappe.db.exists("User", "test@getgrowth.app") → 不为 None
  2. 浏览器打开 https://gottenergy.getgrowth.app → 用 test 账号登录 → 成功进入 Frappe Desk

🧠 涉及到的代码逻辑

文件关键方法/行号做了什么
frappe/utils/password.pyupdate_password(user, pwd) L120密码哈希写入 __Auth
frappe/model/document.pyDocument.insert()User Doctype 插入
frappe/model/document.pyDocument.reload()从 DB 重新加载文档
frappe/core/doctype/user/user.pyUser DocType 定义用户字段与权限

流程简述

  1. get_doc 创建 User 文档对象 → insert() 写入 tabUser
  2. reload() 从数据库读回 → append("roles") 添加 Has Role 子表记录
  3. update_password() 写入 __Auth 表(独立的认证表)
  4. 登录时 Frappe 从 __Auth 读取密码哈希做比对

💡 总结经验

  • 这次最大的教训update_password() 签名是 (user, pwd) → 用户名在前,密码在后。传 User 对象而不是字符串会报 TypeError
  • 如果再来一次,我会先查 frappe.get_all("Role") 确认角色存在,而不是假设 System User 存在
  • 这个问题的本质其实是 迁移站点(gottenergy)的角色体系不是 Frappe 默认的,System User 是 Framework 自带角色但可能被 ERPNext 覆盖了

🕳️ 踩过的坑 & 被否决的方案


🩺 故障排查决策树

症状:frappe.get_doc("User", "test@...") 报 DoesNotExistError
  → 用户没创建成功或已被回滚
    → 重新从 Step 2 执行,每步单独 commit

症状:登录提示 "登录无效"
  → 查密码是否设成功、角色是否添加
    → print(frappe.db.sql("SELECT password FROM __Auth WHERE name=%s", ("test@getgrowth.app",)))
      → 如果 password 列的 hash 以 $pbkdf2 开头 → 密码正常
      → 如果是 NULL 或空 → 重新跑 update_password

症状:登录成功但 OAuth 跳转后被拒
  → 查 OAuth Client 的 allowed_roles 是否包含用户的角色

🔒 安全 & 成本速查

  • 测试用户用完即删frappe.delete_doc("User", "test@getgrowth.app")
  • 成本:$0

📌 待办 / 未来优化方向

  • 写一个可复用的 create_test_user.py 脚本,参数化站点名和角色名

📋 速查命令合集(复制专用,无解释)

docker exec -i $(docker ps -qf "name=press-backend") ssh root@45.55.93.224 "echo 'import frappe; from frappe.utils.password import update_password; frappe.init(site=\"gottenergy.getgrowth.app\"); frappe.connect(); user = frappe.get_doc({\"doctype\": \"User\", \"email\": \"test@getgrowth.app\", \"first_name\": \"Test\", \"send_welcome_email\": 0, \"enabled\": 1}); user.insert(ignore_permissions=True); frappe.db.commit(); user.reload(); user.append(\"roles\", {\"role\": \"Desk User\"}); user.save(); frappe.db.commit(); update_password(\"test@getgrowth.app\", \"test1234\"); frappe.db.commit(); print(\"done\")' | docker exec -i bench-0003-000007-f1 bench --site gottenergy.getgrowth.app console"
 
frappe.delete_doc("User", "test@getgrowth.app")
frappe.db.commit()

📝 更新日志

日期改动原因
2026-07-09首次创建,记录创建+删除测试用户完整流程SSO 测试需要