# sunderer_app/api/custom_user_signup.py import frappe from frappe import _ # 虽然这里不直接用 _(),但保持导入 from frappe.utils import validate_email_address, random_string, get_url, generate_hash import json # 导入 json 模块 @frappe.whitelist(allow_guest=True) def custom_sign_up(email, full_name, password=None, send_email=True, **kwargs): """ 自定义用户注册 API,模仿 Frappe 的 sign_up 但发送自定义邮件链接 Args: email (str): 用户邮箱 full_name (str): 用户全名 password (str, optional): 用户密码 (如果需要立即设置) send_email (bool, optional): 是否发送激活邮件. Defaults to True. **kwargs: 其他可能传递给 User DocType 的字段 Returns: dict: 包含操作结果的字典 """ try: # --- 1. 验证输入 --- email = validate_email_address(email, throw=True) # 验证邮箱格式 if frappe.db.exists("User", {"email": email}): # 不再使用 frappe.throw,而是直接返回错误信息 return {"status": "error", "message": _("该邮箱地址已被注册。")} if not full_name: return {"status": "error", "message": _("请输入全名。")} # --- 2. 创建 User 文档 --- user = frappe.get_doc({ "doctype": "User", "email": email, "first_name": full_name.split(" ")[0], # 简单拆分姓 "last_name": " ".join(full_name.split(" ")[1:]) if len(full_name.split(" ")) > 1 else "", # 其余部分为名 "enabled": 1 if password else 0, # 如果提供了密码,可以立即启用;否则等待激活 "new_password": password, # 如果提供了密码,设置它 "send_welcome_email": 0, # 我们自己发送邮件 "user_type": "Website User", # 通常注册的是网站用户 # **kwargs: 添加其他通过 kwargs 传递的字段 **kwargs }) # --- 3. 插入用户文档 --- user.insert(ignore_permissions=True) # --- 4. 生成激活密钥 --- # 如果没有提供密码,或者总是需要激活邮件,则生成密钥 if not password: # Use frappe.generate_hash instead of get_password_reset_key reset_key = frappe.generate_hash(length=100) # Generate a secure key else: reset_key = None # 如果密码已设置,可能不需要密钥,或用于其他目的 # --- 5. 保存用户文档 (如果需要更新密钥) --- if reset_key: user.reload() # 重新加载文档以确保它是最新的 user.reset_password_key = reset_key user.save(ignore_permissions=True) # --- 6. 发送自定义激活邮件 --- if send_email and reset_key: send_custom_activation_email(user.email, reset_key) # --- 7. 返回结果 --- if password: return {"status": "success", "message": _("用户注册成功,您现在可以登录。")} else: return {"status": "success", "message": _("激活邮件已发送,请查收邮箱。")} except frappe.DuplicateEntryError: # 这个异常可能在 user.insert() 时由数据库唯一性约束触发 return {"status": "error", "message": _("该邮箱地址已被注册。")} except Exception as e: # 捕获所有其他意外错误 frappe.log_error(f"注册过程中发生未知错误,邮箱: {email}, 错误: {str(e)}") return {"status": "error", "message": _("注册过程中发生未知错误,请稍后再试。")} @frappe.whitelist(allow_guest=True) def send_custom_activation_email(user_email, reset_key): """ 发送包含自定义激活链接的邮件 """ # 构造自定义激活 URL activation_url = f"{get_url()}/activate-account?key={reset_key}" # Use get_url helper # 使用中文主题和内容 subject = _("决裂者终端 - 激活您的账户") # 或者 _("决裂者终端 - 设置密码") message = f"""
您好!
请点击下方链接设置密码并激活您的账户:
如果您未发起此请求,请忽略此邮件。
""" frappe.sendmail( recipients=[user_email], subject=subject, message=message, delayed=False # Send immediately ) @frappe.whitelist(allow_guest=True) def trigger_account_setup_email(email): try: # --- 1. 验证邮箱格式 (可选,但推荐) --- import re pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' if not re.match(pattern, email): return {"exc": "请输入有效的邮箱地址!"} # --- 2. 检查邮箱是否已存在于系统中 --- if frappe.db.exists("User", {"email": email}): return {"exc": "该邮箱地址已被注册。"} # --- 3. 生成唯一的、有时效性的设置密钥 --- setup_key = frappe.generate_hash(length=100) # 生成一个足够长的随机密钥 # --- 4. 创建初始的 User 记录 (禁用) --- # 由于没有全名,我们暂时使用邮箱前缀作为名字,或者可以要求前端提供 username_parts = email.split('@') first_name = username_parts[0] # 使用邮箱前缀作为名字 user_doc = frappe.get_doc({ "doctype": "User", "email": email, "first_name": first_name, "last_name": "", # 可以留空或根据需要填充 "enabled": 0, # 初始状态为禁用 "send_welcome_email": 0, # 我们自己发送邮件 "user_type": "Website User", # 可以根据需要添加其他默认字段 }) user_doc.insert(ignore_permissions=True) # 插入用户,此时是禁用的 print(f"Created initial disabled User: {user_doc.name}") # 调试日志 # --- 5. 创建 Signup Key DocType 记录 --- signup_key_doc = frappe.new_doc("Signup Key") signup_key_doc.key = setup_key # 使用生成的 setup_key 作为 key 字段 signup_key_doc.email = email # 存储关联的邮箱 signup_key_doc.used = 0 # 标记为未使用 # 如果 Signup Key DocType 有 expires_on 字段,可以设置过期时间 # signup_key_doc.expires_on = add_to_date(now_datetime(), hours=1) # 1小时后过期 signup_key_doc.insert(ignore_permissions=True) print(f"Created Signup Key: {signup_key_doc.name} for email: {email}") # 调试日志 # --- 6. 构造 IncreaseAcc 链接 --- site_url = get_url() setup_url = f"{site_url}/IncreaseAcc?key={setup_key}" # --- 7. 发送包含链接的邮件 --- send_setup_link_email(email, setup_url) # --- 8. 返回成功信息 --- return {"message": "注册链接已发送至您的邮箱,请查收。"} except frappe.DuplicateEntryError as e: # 如果 User 或 Signup Key 插入时违反唯一性约束 frappe.log_error(f"Trigger account setup failed due to duplicate entry, email: {email}, error: {str(e)}") return {"exc": "用户或密钥已存在,请稍后再试。"} except Exception as e: # 捕获所有其他意外错误 frappe.log_error(f"触发账户设置邮件失败,邮箱: {email}, 错误: {str(e)}") import traceback frappe.log_error(f"触发账户设置邮件错误堆栈:\n{traceback.format_exc()}") # 如果 User 创建失败,尝试回滚 Signup Key (可选) # frappe.delete_doc_if_exists("Signup Key", setup_key_name, ignore_permissions=True) return {"exc": f"发送失败: {str(e)}"} @frappe.whitelist(allow_guest=True) def send_setup_link_email(user_email, setup_url): """ 发送包含账户设置链接的邮件 """ # 使用中文主题和内容 subject = "决裂者终端 - 完成账户注册" message = f"""您好!
请点击下方链接完成您的账户注册:
此链接将在 1 小时内有效。
如果您未发起此请求,请忽略此邮件。
© 2026 Sunderer Games. All rights reserved.