send_email_code.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """
  2. 发送邮箱验证码脚本
  3. 扫描 verification_codes 表,查找未发送的邮箱验证码并发送
  4. 运行: python send_email_code.py
  5. """
  6. import asyncio
  7. import sys
  8. import logging
  9. from datetime import datetime
  10. from typing import List, Optional
  11. import aiomysql
  12. from sqlalchemy import select
  13. from sqlalchemy.ext.asyncio import AsyncSession
  14. from config import settings
  15. from database import DatabaseManager
  16. from models_db import VerificationCode
  17. logger = logging.getLogger(__name__)
  18. async def send_email_code(email: str, code: str) -> bool:
  19. """
  20. 发送邮箱验证码
  21. Args:
  22. email: 邮箱地址
  23. code: 验证码
  24. Returns:
  25. 是否发送成功
  26. """
  27. # TODO: 实现邮箱发送 API 调用
  28. # 示例:
  29. # async with httpx.AsyncClient() as client:
  30. # response = await client.post(
  31. # "https://api.email-provider.com/send",
  32. # json={
  33. # "to": email,
  34. # "subject": "验证码",
  35. # "template": "verification_code",
  36. # "code": code
  37. # }
  38. # )
  39. # return response.status_code == 200
  40. print(f"📧 [TODO] 发送邮箱验证码: email={email}, code={code}")
  41. return False
  42. async def process_email_codes():
  43. """扫描并发送邮箱验证码"""
  44. db_manager = DatabaseManager()
  45. try:
  46. await db_manager.connect()
  47. print("✅ 数据库连接成功")
  48. async with db_manager.get_session() as session:
  49. # 查询未发送的邮箱验证码
  50. result = await session.execute(
  51. select(VerificationCode)
  52. .where(
  53. VerificationCode.send_type == "email",
  54. VerificationCode.is_sent == False,
  55. VerificationCode.is_valid == True,
  56. VerificationCode.email.isnot(None)
  57. )
  58. .order_by(VerificationCode.created_at.asc())
  59. .limit(100) # 每次处理100条
  60. )
  61. codes = result.scalars().all()
  62. if not codes:
  63. print("ℹ️ 没有待发送的邮箱验证码")
  64. return
  65. print(f"📋 找到 {len(codes)} 条待发送的邮箱验证码")
  66. success_count = 0
  67. fail_count = 0
  68. for code_record in codes:
  69. try:
  70. # 发送邮箱
  71. success = await send_email_code(code_record.email, code_record.code)
  72. if success:
  73. # 更新发送状态
  74. code_record.is_sent = True
  75. code_record.sent_at = datetime.now()
  76. code_record.metadata_json = code_record.metadata_json or {}
  77. code_record.metadata_json["send_status"] = "success"
  78. code_record.metadata_json["send_at"] = datetime.now().isoformat()
  79. success_count += 1
  80. print(f"✅ 发送成功: code_id={code_record.code_id}, email={code_record.email}")
  81. else:
  82. # 记录失败信息
  83. code_record.metadata_json = code_record.metadata_json or {}
  84. code_record.metadata_json["send_status"] = "failed"
  85. code_record.metadata_json["error"] = "API调用失败"
  86. fail_count += 1
  87. print(f"❌ 发送失败: code_id={code_record.code_id}, email={code_record.email}")
  88. await session.commit()
  89. except Exception as e:
  90. print(f"❌ 处理验证码失败: code_id={code_record.code_id}, error={e}")
  91. code_record.metadata_json = code_record.metadata_json or {}
  92. code_record.metadata_json["send_status"] = "error"
  93. code_record.metadata_json["error"] = str(e)
  94. await session.commit()
  95. fail_count += 1
  96. print(f"\n📊 处理完成: 成功 {success_count} 条, 失败 {fail_count} 条")
  97. except Exception as e:
  98. print(f"❌ 处理失败: {e}")
  99. import traceback
  100. traceback.print_exc()
  101. sys.exit(1)
  102. finally:
  103. await db_manager.close()
  104. if __name__ == "__main__":
  105. logging.basicConfig(
  106. level=logging.INFO,
  107. format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
  108. datefmt='%Y-%m-%d %H:%M:%S'
  109. )
  110. asyncio.run(process_email_codes())