本文目录导读:

我来为您设计一个PHP项目中的短信平台多供应商架构方案。
整体架构设计
核心接口定义
<?php
// interfaces/SmsProviderInterface.php
interface SmsProviderInterface
{
/**
* 发送短信
* @param string $phone 手机号
* @param string $content 短信内容
* @param array $options 附加选项
* @return SmsResult
*/
public function send(string $phone, string $content, array $options = []): SmsResult;
/**
* 发送批量短信
* @param array $messages 消息数组 [['phone'=>'', 'content'=>'']]
* @return array SmsResult[]
*/
public function sendBatch(array $messages): array;
/**
* 查询余额
* @return float
*/
public function queryBalance(): float;
/**
* 获取供应商名称
* @return string
*/
public function getProviderName(): string;
/**
* 检查供应商健康状态
* @return bool
*/
public function healthCheck(): bool;
}
// Result/SmsResult.php
class SmsResult
{
public bool $success;
public string $messageId;
public string $provider;
public string $errorMessage;
public array $rawResponse;
public float $cost;
public int $timestamp;
}
供应商实现示例
<?php
// Providers/AliyunSmsProvider.php
class AliyunSmsProvider implements SmsProviderInterface
{
private $accessKeyId;
private $accessKeySecret;
private $signName;
public function __construct(array $config)
{
$this->accessKeyId = $config['access_key_id'];
$this->accessKeySecret = $config['access_key_secret'];
$this->signName = $config['sign_name'];
}
public function send(string $phone, string $content, array $options = []): SmsResult
{
// 阿里云短信发送逻辑
// 使用阿里云SDK
$result = new SmsResult();
try {
// 实际API调用
$response = $this->callApi($phone, $content);
$result->success = true;
$result->messageId = $response['MessageId'];
$result->provider = $this->getProviderName();
$result->timestamp = time();
} catch (Exception $e) {
$result->success = false;
$result->errorMessage = $e->getMessage();
}
return $result;
}
public function getProviderName(): string
{
return 'aliyun';
}
// ... 其他方法实现
}
// Providers/TencentSmsProvider.php
class TencentSmsProvider implements SmsProviderInterface
{
// 腾讯云短信实现
}
// Providers/SubmailSmsProvider.php
class SubmailSmsProvider implements SmsProviderInterface
{
// SUBMAIL短信实现
}
策略管理器
<?php
// Services/SmsManager.php
class SmsManager
{
private array $providers = [];
private array $config;
public function __construct(array $config = [])
{
$this->config = $config;
$this->initProviders();
}
private function initProviders(): void
{
foreach ($this->config['providers'] as $name => $providerConfig) {
if ($providerConfig['enabled']) {
$className = $providerConfig['class'];
$this->providers[$name] = new $className($providerConfig['config']);
}
}
}
/**
* 智能发送 - 自动选择供应商
*/
public function smartSend(string $phone, string $content, string $strategy = 'weighted'): SmsResult
{
// 1. 获取可用供应商列表
$availableProviders = $this->getAvailableProviders();
if (empty($availableProviders)) {
throw new RuntimeException('No available SMS providers');
}
// 2. 根据策略选择供应商
$selectedProvider = $this->selectProvider($availableProviders, $strategy);
// 3. 发送短信
$result = $selectedProvider->send($phone, $content);
// 4. 如果失败且需要容错
if (!$result->success && $this->config['failover_enabled']) {
$result = $this->failoverSend($phone, $content, $selectedProvider->getProviderName());
}
return $result;
}
/**
* 故障转移发送
*/
private function failoverSend(string $phone, string $content, string $excludeProvider): SmsResult
{
$backupProviders = array_filter(
$this->providers,
fn($p) => $p->getProviderName() !== $excludeProvider
);
foreach ($backupProviders as $provider) {
$result = $provider->send($phone, $content);
if ($result->success) {
return $result;
}
}
throw new RuntimeException('All SMS providers failed');
}
/**
* 获取可用供应商
*/
private function getAvailableProviders(): array
{
$available = [];
foreach ($this->providers as $name => $provider) {
if ($provider->healthCheck()) {
$available[$name] = $provider;
}
}
return $available;
}
/**
* 选择供应商策略
*/
private function selectProvider(array $providers, string $strategy): SmsProviderInterface
{
switch ($strategy) {
case 'random':
return $providers[array_rand($providers)];
case 'priority':
// 按配置优先级选择
uasort($providers, function($a, $b) {
return $this->getProviderPriority($a) <=> $this->getProviderPriority($b);
});
return array_shift($providers);
case 'weighted':
// 加权轮询
return $this->weightedRoundRobin($providers);
default:
return array_shift($providers);
}
}
/**
* 加权轮询算法
*/
private function weightedRoundRobin(array $providers): SmsProviderInterface
{
static $currentIndex = 0;
static $currentWeight = 0;
$maxWeight = $this->getMaxWeight($providers);
$gcd = $this->getGcdWeights($providers);
while (true) {
$currentIndex = ($currentIndex + 1) % count($providers);
if ($currentIndex == 0) {
$currentWeight = $currentWeight - $gcd;
if ($currentWeight <= 0) {
$currentWeight = $maxWeight;
if ($currentWeight == 0) {
return reset($providers);
}
}
}
$provider = array_values($providers)[$currentIndex];
$weight = $this->getProviderWeight($provider);
if ($weight >= $currentWeight) {
return $provider;
}
}
}
}
配置管理
<?php
// config/sms.php
return [
'default_provider' => 'aliyun',
'failover_enabled' => true,
'failover_threshold' => 3, // 失败次数阈值
'providers' => [
'aliyun' => [
'enabled' => true,
'class' => AliyunSmsProvider::class,
'priority' => 1,
'weight' => 5,
'config' => [
'access_key_id' => env('ALIYUN_SMS_ACCESS_KEY_ID'),
'access_key_secret' => env('ALIYUN_SMS_ACCESS_KEY_SECRET'),
'sign_name' => env('ALIYUN_SMS_SIGN_NAME'),
'template_code' => 'SMS_123456789',
'region_id' => 'cn-hangzhou',
]
],
'tencent' => [
'enabled' => true,
'class' => TencentSmsProvider::class,
'priority' => 2,
'weight' => 3,
'config' => [
'secret_id' => env('TENCENT_SMS_SECRET_ID'),
'secret_key' => env('TENCENT_SMS_SECRET_KEY'),
'app_id' => env('TENCENT_SMS_APP_ID'),
'sign_name' => env('TENCENT_SMS_SIGN_NAME'),
'template_id' => '123456',
]
],
'submail' => [
'enabled' => true,
'class' => SubmailSmsProvider::class,
'priority' => 3,
'weight' => 2,
'config' => [
'app_id' => env('SUBMAIL_APP_ID'),
'app_key' => env('SUBMAIL_APP_KEY'),
'sign_type' => 'normal',
]
],
],
'strategies' => [
'default' => 'weighted',
'high_priority' => 'priority',
'load_balance' => 'random',
],
'rate_limits' => [
'per_minute' => 20,
'per_hour' => 500,
'per_day' => 3000,
],
'templates' => [
'verification_code' => [
'aliyun' => 'SMS_123456789',
'tencent' => '123456',
'submail' => '12345',
],
'notification' => [
'aliyun' => 'SMS_987654321',
'tencent' => '654321',
],
],
];
高级功能实现
监控与统计
<?php
// Services/SmsMonitor.php
class SmsMonitor
{
private $metrics = [];
private $db;
/**
* 记录发送日志
*/
public function logSend(SmsResult $result): void
{
$logData = [
'phone' => $result->phone,
'content' => $result->content,
'provider' => $result->provider,
'success' => $result->success,
'message_id' => $result->messageId,
'error_message' => $result->errorMessage,
'cost' => $result->cost,
'created_at' => date('Y-m-d H:i:s'),
'ip' => $_SERVER['REMOTE_ADDR'] ?? '',
];
// 存储到数据库
$this->db->table('sms_logs')->insert($logData);
// 更新统计指标
$this->updateMetrics($result);
}
/**
* 获取供应商成功率统计
*/
public function getProviderStats(string $provider, string $startDate, string $endDate): array
{
return $this->db->table('sms_logs')
->where('provider', $provider)
->whereBetween('created_at', [$startDate, $endDate])
->selectRaw('
COUNT(*) as total,
SUM(CASE WHEN success = 1 THEN 1 ELSE 0 END) as success_count,
AVG(CAST(cost AS DECIMAL(10,4))) as avg_cost
')
->first();
}
/**
* 告警检查
*/
public function checkAlerts(): void
{
// 检查各供应商成功率
foreach ($this->getAllProviders() as $provider) {
$stats = $this->getProviderStats(
$provider,
date('Y-m-d H:i:s', strtotime('-5 minutes')),
date('Y-m-d H:i:s')
);
// 如果成功率低于阈值,发送告警
if ($stats['total'] > 10 &&
($stats['success_count'] / $stats['total']) < 0.9) {
$this->sendAlert("Provider {$provider} success rate below 90%");
}
}
}
}
缓存与限流
<?php
// Services/SmsRateLimiter.php
class SmsRateLimiter
{
private $cache;
private $config;
public function __construct($cache, array $config)
{
$this->cache = $cache;
$this->config = $config;
}
/**
* 检查是否可以发送
*/
public function canSend(string $phone): bool
{
$key = "sms_rate_limit:{$phone}";
$minuteKey = "{$key}:minute";
$hourKey = "{$key}:hour";
$dayKey = "{$key}:day";
$minuteCount = $this->cache->get($minuteKey, 0);
$hourCount = $this->cache->get($hourKey, 0);
$dayCount = $this->cache->get($dayKey, 0);
if ($minuteCount >= $this->config['per_minute']) {
return false;
}
if ($hourCount >= $this->config['per_hour']) {
return false;
}
if ($dayCount >= $this->config['per_day']) {
return false;
}
return true;
}
/**
* 记录发送
*/
public function recordSend(string $phone): void
{
$key = "sms_rate_limit:{$phone}";
$this->cache->increment("{$key}:minute");
$this->cache->expire("{$key}:minute", 60);
$this->cache->increment("{$key}:hour");
$this->cache->expire("{$key}:hour", 3600);
$this->cache->increment("{$key}:day");
$this->cache->expire("{$key}:day", 86400);
}
}
使用示例
<?php
// 使用示例
class SmsService
{
private SmsManager $smsManager;
private SmsMonitor $monitor;
private SmsRateLimiter $rateLimiter;
public function __construct()
{
$config = require 'config/sms.php';
$this->smsManager = new SmsManager($config);
$this->monitor = new SmsMonitor();
$this->rateLimiter = new SmsRateLimiter(new Redis(), $config['rate_limits']);
}
/**
* 发送验证码
*/
public function sendVerificationCode(string $phone): array
{
// 检查限流
if (!$this->rateLimiter->canSend($phone)) {
throw new RuntimeException('SMS rate limit exceeded');
}
// 生成验证码
$code = random_int(100000, 999999);
$content = "您的验证码是:{$code},5分钟内有效。";
// 智能发送
$result = $this->smsManager->smartSend($phone, $content, 'weighted');
// 记录日志
$result->content = $content;
$result->phone = $phone;
$this->monitor->logSend($result);
// 记录限流
$this->rateLimiter->recordSend($phone);
// 缓存验证码
Cache::put("sms_code:{$phone}", $code, 300);
return [
'success' => $result->success,
'message_id' => $result->messageId,
'provider' => $result->provider,
];
}
/**
* 批量发送通知
*/
public function batchSendNotification(array $phones, string $template): array
{
$messages = [];
foreach ($phones as $phone) {
$messages[] = [
'phone' => $phone,
'content' => $this->buildTemplate($template, ['name' => 'User']),
];
}
// 获取最佳供应商
$provider = $this->smsManager->getOptimalProvider('notification');
// 发送
$results = $provider->sendBatch($messages);
// 记录所有结果
foreach ($results as $result) {
$this->monitor->logSend($result);
}
return [
'total' => count($results),
'success' => count(array_filter($results, fn($r) => $r->success)),
'failed' => count(array_filter($results, fn($r) => !$r->success)),
'details' => $results,
];
}
}
数据库表设计
-- 短信日志表
CREATE TABLE `sms_logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`phone` varchar(20) NOT NULL COMMENT '手机号',
`content` text NOT NULL COMMENT '短信内容',
`provider` varchar(50) NOT NULL COMMENT '供应商',
`success` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否成功',
`message_id` varchar(100) DEFAULT NULL COMMENT '消息ID',
`error_message` text COMMENT '错误信息',
`cost` decimal(10,4) DEFAULT '0.0000' COMMENT '费用',
`ip` varchar(45) DEFAULT NULL COMMENT '请求IP',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`),
KEY `idx_provider` (`provider`),
KEY `idx_created_at` (`created_at`),
KEY `idx_success` (`success`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信发送日志';
-- 供应商配置表
CREATE TABLE `sms_provider_configs` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '供应商名称',
`config` json NOT NULL COMMENT '配置信息',
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`priority` int(11) NOT NULL DEFAULT '0' COMMENT '优先级',
`weight` int(11) NOT NULL DEFAULT '1' COMMENT '权重',
`created_at` datetime DEFAULT NULL,
`updated_at` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短信供应商配置';
-- 供应商健康检查表
CREATE TABLE `sms_health_checks` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`provider` varchar(50) NOT NULL,
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1:健康 0:不健康',
`response_time` int(11) DEFAULT NULL COMMENT '响应时间(ms)',
`error_message` text COMMENT '错误信息',
`checked_at` datetime DEFAULT NULL COMMENT '检查时间',
PRIMARY KEY (`id`),
KEY `idx_provider_checked` (`provider`, `checked_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商健康检查记录';
这个方案提供了完整的短信平台多供应商支持,包括:
- 统一的接口定义 - 所有供应商实现统一接口
- 多种选择策略 - 权重轮询、优先级、随机等
- 故障转移 - 自动切换到备用供应商
- 监控统计 - 记录发送日志和统计
- 限流控制 - 防止滥用
- 健康检查 - 自动检测供应商可用性
- 配置化管理 - 动态调整供应商配置
您可以根据实际需求调整供应商的具体实现代码。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播