// +---------------------------------------------------------------------- declare (strict_types = 1); namespace addon\smssend\subscribe; use ywxapp\service\AddonService; use ywxapp\utils\HttpClient; use think\facade\Env; use think\facade\Log; /** * 生产版短信发送订阅器 * * 监听 ywxapp\library\Sms 触发的事件: * - onSend($sms) -> SmsSend 验证码下发(返回 true/false 决定 Sms::send 是否成功落库) * - onNotice($sms) -> SmsNotice 通知类短信(可选) * * 开发环境(app_debug=1)下不真正外发,避免无谓网络请求;生产环境调用配置的网关。 * * 注意:ywxapp\service\AppService 默认已注册一个 SmsSend 监听—— * 开发环境返回 true(仅记录验证码);生产环境返回 null(交由本插件真正发送)。 * 因此生产环境务必启用本插件,否则 Sms::send 将因无监听器返回真值而失败。 */ class Sender { protected $eventPrefix = 'Sms'; /** * 下发验证码 * @param mixed $sms Sms 模型对象,可用 $sms['mobile']/$sms['code']/$sms['event'] 访问 * @return bool 发送是否成功(true 才会保留短信记录) */ public function onSend($sms): bool { $cfg = AddonService::config('smssend'); $api = $cfg['api_url'] ?? ''; if (!$api) { Log::warning('[smssend] 未配置 api_url,无法发送短信'); return false; } // 开发环境不真正外发,避免无谓网络请求与密钥泄露 if (Env::get('app_debug')) { Log::info('[smssend][DEV] 模拟发送 -> ' . ($sms['mobile'] ?? '') . ' code=' . ($sms['code'] ?? '')); return true; } $sign = $cfg['sign'] ?? ''; $content = '【' . $sign . '】' . str_replace( ['{code}', '{mobile}'], [$sms['code'] ?? '', $sms['mobile'] ?? ''], $cfg['template'] ?? '您的验证码为:{code},请勿泄露给他人' ); $params = [ 'u' => $cfg['account'] ?? '', 'p' => $cfg['password'] ?? '', 'm' => $sms['mobile'], 'c' => $content, ]; try { $client = new HttpClient(); $resp = $client->get($api, $params); if ($resp === false) { Log::error('[smssend] 请求失败: ' . $client->getError()); return false; } // 示例按短信宝协议判断:返回 "0" 表示成功,其余为错误码 $resp = trim((string) $resp); if ($resp === '0') { return true; } Log::error('[smssend] 网关返回异常: ' . $resp); return false; } catch (\Throwable $e) { Log::error('[smssend] 发送异常: ' . $e->getMessage()); return false; } } /** * 通知类短信(可选实现) */ public function onNotice($sms): bool { return true; } }