erp-backend/app/Services/SmsService.php
2026-04-01 17:07:04 +08:00

170 lines
5.2 KiB
PHP

<?php
namespace App\Services;
use App\Models\SystemConfig;
use Illuminate\Support\Facades\Cache;
class SmsService
{
/**
* 发送验证码
*/
public function sendCode(string $phone, string $type = 'login', int $expireMinutes = 5): array
{
$config = $this->getConfig();
if (!$config['enabled']) {
return ['success' => false, 'message' => '短信服务未启用'];
}
// 生成6位验证码
$code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
// 存储验证码
$cacheKey = "sms_code:{$phone}:{$type}";
Cache::put($cacheKey, [
'code' => $code,
'expires_at' => now()->addMinutes($expireMinutes)->timestamp,
], now()->addMinutes($expireMinutes + 5));
// 调用阿里云短信
if ($config['driver'] === 'aliyun') {
return $this->sendAliyunSms($phone, $code, $type, $config);
}
// 模拟模式(开发环境)
\Log::info("SMS Code [{$type}] for {$phone}: {$code}");
return [
'success' => true,
'message' => '验证码已发送(模拟模式)',
'data' => ['code' => $code], // 开发环境返回验证码
];
}
/**
* 验证验证码
*/
public function verifyCode(string $phone, string $code, string $type = 'login'): bool
{
$cacheKey = "sms_code:{$phone}:{$type}";
$data = Cache::get($cacheKey);
if (!$data) {
return false;
}
if ($data['expires_at'] < now()->timestamp) {
Cache::forget($cacheKey);
return false;
}
if ($data['code'] !== $code) {
return false;
}
// 验证成功后删除
Cache::forget($cacheKey);
return true;
}
/**
* 获取短信配置
*/
public function getConfig(): array
{
$cacheKey = 'sms_service_config';
return Cache::remember($cacheKey, 3600, function () {
return [
'enabled' => SystemConfig::getValue('sms', 'enabled', false),
'driver' => SystemConfig::getValue('sms', 'driver', 'aliyun'),
'access_key_id' => SystemConfig::getValue('sms', 'access_key_id', ''),
'access_key_secret' => SystemConfig::getValue('sms', 'access_key_secret', ''),
'sign_name' => SystemConfig::getValue('sms', 'sign_name', ''),
'template_codes' => SystemConfig::getValue('sms', 'template_codes', []),
];
});
}
/**
* 阿里云短信发送
*/
private function sendAliyunSms(string $phone, string $code, string $type, array $config): array
{
$templateCode = $this->getTemplateCode($type, $config['template_codes']);
if (!$templateCode) {
return ['success' => false, 'message' => '未配置短信模板'];
}
try {
$params = [
'PhoneNumbers' => $phone,
'SignName' => $config['sign_name'],
'TemplateCode' => $templateCode,
'TemplateParam' => json_encode(['code' => $code]),
];
// 阿里云 OpenAPI 签名
$signResult = $this->aliyunSign($config['access_key_id'], $config['access_key_secret'], $params);
$response = \Illuminate\Support\Facades\Http::withHeaders([
'Content-Type' => 'application/x-www-form-urlencoded',
])->asForm()->post('https://dysmsapi.aliyuncs.com/', array_merge($params, $signResult));
$result = $response->json();
if (($result['Code'] ?? '') === 'OK') {
return ['success' => true, 'message' => '发送成功'];
}
return ['success' => false, 'message' => $result['Message'] ?? '发送失败'];
} catch (\Exception $e) {
\Log::error('阿里云短信发送失败: ' . $e->getMessage());
return ['success' => false, 'message' => '短信发送失败'];
}
}
/**
* 获取模板代码
*/
private function getTemplateCode(string $type, array $templates): ?string
{
$map = [
'login' => $templates['login'] ?? null,
'reset_password' => $templates['reset_password'] ?? $templates['reset'] ?? null,
'pair' => $templates['pair'] ?? $templates['login'] ?? null,
];
return $map[$type] ?? null;
}
/**
* 阿里云 API 签名
*/
private function aliyunSign(string $accessKeyId, string $accessKeySecret, array $params): array
{
$params['AccessKeyId'] = $accessKeyId;
$params['Format'] = 'JSON';
$params['SignatureMethod'] = 'HMAC-SHA1';
$params['SignatureVersion'] = '1.0';
$params['SignatureNonce'] = uniqid();
$params['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
$params['Version'] = '2017-05-25';
ksort($params);
$stringToSign = 'POST&%2F&' . urlencode(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
$signature = base64_encode(
hash_hmac('sha1', $stringToSign, $accessKeySecret . '&', true)
);
$params['Signature'] = $signature;
return $params;
}
}