SmsService.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. namespace App\Module\Sms\Services;
  3. use App\Module\Sms\Enums\Code;
  4. use App\Module\Sms\Enums\CODE_TYPE;
  5. use App\Module\Sms\Gateway\MyGateway;
  6. use App\Module\Sms\Models\SmsCode;
  7. use App\Module\System\Services\Config;
  8. use Carbon\Carbon;
  9. use Overtrue\EasySms\EasySms;
  10. use Overtrue\EasySms\Exceptions\InvalidArgumentException;
  11. use Overtrue\EasySms\Exceptions\NoGatewayAvailableException;
  12. use UCore\Exception\LogicException;
  13. use UCore\Helper\Logger;
  14. /**
  15. * 短信服务类
  16. */
  17. class SmsService
  18. {
  19. /**
  20. * 验证码有效期(秒)
  21. */
  22. public const CODE_EXPIRE_TIME = 600;
  23. /**
  24. * @var EasySms
  25. */
  26. protected $easySms;
  27. /**
  28. * 构造函数
  29. */
  30. public function __construct()
  31. {
  32. $this->initEasySms();
  33. }
  34. /**
  35. * 初始化 EasySms 实例
  36. */
  37. protected function initEasySms(): void
  38. {
  39. // 获取短信配置
  40. $list = \App\Module\System\Services\ConfigService::getByGroup("短信配置");
  41. $gateways = [];
  42. $gatewayConfigs = [];
  43. foreach ($list as $item) {
  44. if($item['is_open']){
  45. $gateways[] = $item['driver'];
  46. $gatewayConfigs[$item['driver']] = $item;
  47. }
  48. }
  49. // 获取基础配置
  50. $config = config('easysms');
  51. $config['default']['gateways'] = $gateways;
  52. $config['gateways'] = $gatewayConfigs;
  53. // 创建实例
  54. $this->easySms = new EasySms($config);
  55. // 注册自定义网关
  56. $this->easySms->extend('mygateway', function ($gatewayConfig) {
  57. return new MyGateway($gatewayConfig);
  58. });
  59. }
  60. /**
  61. * 发送验证码
  62. *
  63. * @param CODE_TYPE $type 验证码类型
  64. * @param string $phone 手机号
  65. * @param string $token 令牌
  66. * @return bool
  67. * @throws LogicException
  68. * @throws InvalidArgumentException
  69. */
  70. public function sendCode(CODE_TYPE $type, string $phone, string $token): bool
  71. {
  72. // 验证类型
  73. if (!Code::isValid($type)) {
  74. throw new LogicException('不存在的验证码类型');
  75. }
  76. // 生成验证码
  77. $code = mt_rand(100000, 999999);
  78. // 获取消息类
  79. $messageClass = Code::getTemplateClass($type);
  80. if (!$messageClass) {
  81. throw new LogicException('未找到对应的消息模板');
  82. }
  83. try {
  84. // 发送短信
  85. $result = $this->easySms->send($phone, new $messageClass([
  86. 'code' => $code,
  87. 'phone' => $phone
  88. ]));
  89. // 记录日志
  90. Logger::debug('sms.send', $result);
  91. // 保存验证码
  92. $smsCode = new SmsCode();
  93. $smsCode->mobile = $phone;
  94. $smsCode->token = $token;
  95. $smsCode->code_value = $code;
  96. $smsCode->type = $type->value;
  97. $smsCode->save();
  98. return true;
  99. } catch (NoGatewayAvailableException $e) {
  100. // 记录错误日志
  101. Logger::error('sms.error', [
  102. 'message' => $e->getLastException()->getMessage(),
  103. 'trace' => $e->getLastException()->getTraceAsString()
  104. ]);
  105. throw new LogicException('短信发送失败');
  106. }
  107. }
  108. /**
  109. * 验证短信验证码
  110. *
  111. * @param CODE_TYPE|int $type 验证码类型
  112. * @param string $phone 手机号
  113. * @param string $code 验证码
  114. * @return bool
  115. */
  116. public function verifyCode(CODE_TYPE|int $type, string $phone, string $code): bool
  117. {
  118. if (empty($phone) || empty($code)) {
  119. return false;
  120. }
  121. $typeValue = $type instanceof CODE_TYPE ? $type->value : $type;
  122. // 获取最新的验证码记录
  123. $smsCode = SmsCode::query()
  124. ->where('mobile', $phone)
  125. ->where('type', $typeValue)
  126. ->orderByDesc('id')
  127. ->first();
  128. if (!$smsCode) {
  129. return false;
  130. }
  131. // 检查验证码是否过期
  132. if ($smsCode->created_at->lt(Carbon::now()->subSeconds(self::CODE_EXPIRE_TIME))) {
  133. $smsCode->forceDelete();
  134. return false;
  135. }
  136. // 验证码匹配检查
  137. if ($smsCode->code_value === $code) {
  138. $smsCode->forceDelete();
  139. return true;
  140. }
  141. return false;
  142. }
  143. }