send_international_sms_code.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. """
  2. 发送国外短信验证码脚本
  3. 扫描 verification_codes 表,查找未发送的国外短信验证码并发送
  4. 运行: python send_international_sms_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_international_sms(phone: str, code: str) -> bool:
  19. """
  20. 发送国外短信验证码
  21. Args:
  22. phone: 手机号(国际格式)
  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.international-sms-provider.com/send",
  32. # json={
  33. # "phone": phone,
  34. # "code": code,
  35. # "template": "verification_code",
  36. # "country_code": extract_country_code(phone)
  37. # }
  38. # )
  39. # return response.status_code == 200
  40. print(f"📱 [TODO] 发送国外短信: phone={phone}, code={code}")
  41. return False
  42. async def process_international_sms_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 == "international_sms",
  54. VerificationCode.is_sent == False,
  55. VerificationCode.is_valid == True,
  56. VerificationCode.phone.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_international_sms(code_record.phone, 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}, phone={code_record.phone}")
  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}, phone={code_record.phone}")
  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_international_sms_codes())