mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-11-07 19:40:49 +08:00
feat(notice): PHPMailer 邮箱驱动与短信公众号骨架
This commit is contained in:
85
extend/base/common/notice/channel/EmailChannel.php
Normal file
85
extend/base/common/notice/channel/EmailChannel.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\notice\channel;
|
||||
|
||||
use base\common\notice\NoticeChannelInterface;
|
||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* 邮件渠道驱动(PHPMailer SMTP).
|
||||
*
|
||||
* 读取 sysconfig notify 组 SMTP 参数执行真实投递:
|
||||
* 抛出异常视为投递失败,异常消息携带 PHPMailer ErrorInfo(含 SMTP 会话 lastError),
|
||||
* timer 消费者据此退避重试直至终态。
|
||||
*/
|
||||
class EmailChannel implements NoticeChannelInterface
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'email';
|
||||
}
|
||||
|
||||
public function send(array $target, array $notice): bool
|
||||
{
|
||||
// 全局总闸:DB 值为字符串,(int) 强转判断;仅 '1' 视为启用(缺省/0 均为关,
|
||||
// 与系统配置 notify 组 notify_email_enable 默认值 0 一致),关闭即抛异常。
|
||||
if ((int) sysconfig('notify', 'notify_email_enable') !== 1) {
|
||||
throw new RuntimeException('邮件发送失败: 渠道未启用(notify_email_enable != 1)');
|
||||
}
|
||||
|
||||
$host = (string) sysconfig('notify', 'notify_smtp_host');
|
||||
$port = (int) sysconfig('notify', 'notify_smtp_port');
|
||||
$secure = strtolower((string) sysconfig('notify', 'notify_smtp_secure'));
|
||||
$user = (string) sysconfig('notify', 'notify_smtp_user');
|
||||
$pass = (string) sysconfig('notify', 'notify_smtp_pass');
|
||||
$from = (string) sysconfig('notify', 'notify_smtp_from', 'notice@localhost');
|
||||
$fromName = (string) sysconfig('notify', 'notify_smtp_from_name');
|
||||
|
||||
$title = (string) ($notice['title'] ?? '');
|
||||
$content = (string) ($notice['content'] ?? '');
|
||||
|
||||
$mailer = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
$mailer->isSMTP();
|
||||
$mailer->Host = $host;
|
||||
$mailer->Port = $port;
|
||||
// SMTPSecure 映射:ssl→隐式 SMTPS;tls→STARTTLS;none/未知→明文。
|
||||
// 明文场景必须显式关闭 SMTPAutoTLS:PHPMailer 默认会对明文会话自动尝试
|
||||
// STARTTLS 升级,Mailpit 等纯明文本地服务会被误判为失败。
|
||||
if ($secure === 'ssl') {
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
} elseif ($secure === 'tls') {
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
} else {
|
||||
$mailer->SMTPSecure = '';
|
||||
$mailer->SMTPAutoTLS = false;
|
||||
}
|
||||
// SMTP 认证仅在配置了用户名时启用(Mailpit 等本地无认证服务不设置)
|
||||
if ($user !== '') {
|
||||
$mailer->SMTPAuth = true;
|
||||
$mailer->Username = $user;
|
||||
$mailer->Password = $pass;
|
||||
}
|
||||
$mailer->CharSet = 'UTF-8';
|
||||
$mailer->isHTML(true);
|
||||
$mailer->setFrom($from, $fromName);
|
||||
$mailer->addAddress((string) ($target['target_addr'] ?? ''));
|
||||
$mailer->Subject = $title;
|
||||
$mailer->Body = '<div style="font-family:Arial,sans-serif;">'
|
||||
. '<h3>' . $title . '</h3>'
|
||||
. '<div>' . nl2br($content) . '</div>'
|
||||
. '</div>';
|
||||
|
||||
$mailer->send();
|
||||
} catch (PHPMailerException $e) {
|
||||
// send() 抛出的 Exception 消息即 $mailer->ErrorInfo(setError 已拼接
|
||||
// SMTP lastError/detail/smtp_code),异常链保留原始异常供上游追溯
|
||||
throw new RuntimeException('邮件发送失败: ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
46
extend/base/common/notice/channel/SmsChannel.php
Normal file
46
extend/base/common/notice/channel/SmsChannel.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\notice\channel;
|
||||
|
||||
use base\common\notice\NoticeChannelInterface;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 短信渠道驱动(骨架).
|
||||
*
|
||||
* 骨架形态:读开关、记日志、恒成功,不执行真实外呼。
|
||||
* 正常业务流不会产生 sms 投递行(scenes() 注册渠道不含 sms),
|
||||
* 行进队列的场景是 app 层注册新场景引入 sms 渠道之后。
|
||||
*/
|
||||
class SmsChannel implements NoticeChannelInterface
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'sms';
|
||||
}
|
||||
|
||||
public function send(array $target, array $notice): bool
|
||||
{
|
||||
/*
|
||||
* TODO 下游对接指引(真实短信发送):
|
||||
* - 使用既有 guzzlehttp/guzzle 客户端调用短信服务商 API(阿里云/腾讯云 SMS 等);
|
||||
* - 读取 sysconfig('notify','notify_sms_sign'/'notify_sms_appid'/'notify_sms_secret')
|
||||
* 组装签名与模板参数,POST 服务商接口;
|
||||
* - 服务商接口非成功码(HTTP 非 2xx 或业务失败码)时抛出 RuntimeException,
|
||||
* 异常消息携带服务商返回的失败信息,交由 timer 消费者退避重试;
|
||||
* - target_addr 为手机号,notice 为通知主体(title/content)。
|
||||
*/
|
||||
|
||||
// 骨架驱动模拟发送:开关状态仅记入日志(关闭也恒成功),不拦截投递、不真实外呼
|
||||
$enabled = (int) sysconfig('notify', 'notify_sms_enable');
|
||||
Log::info('notice sms channel skeleton simulated send', [
|
||||
'channel' => self::name(),
|
||||
'enabled' => $enabled,
|
||||
'notice_id' => $notice['id'] ?? 0,
|
||||
'target_addr' => $target['target_addr'] ?? '',
|
||||
'title' => $notice['title'] ?? '',
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
46
extend/base/common/notice/channel/WechatChannel.php
Normal file
46
extend/base/common/notice/channel/WechatChannel.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\notice\channel;
|
||||
|
||||
use base\common\notice\NoticeChannelInterface;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 公众号渠道驱动(骨架).
|
||||
*
|
||||
* 骨架形态:读开关、记日志、恒成功,不执行真实外呼。
|
||||
* 正常业务流不会产生 wechat 投递行(scenes() 注册渠道不含 wechat),
|
||||
* 行进队列的场景是 app 层注册新场景引入 wechat 渠道之后。
|
||||
*/
|
||||
class WechatChannel implements NoticeChannelInterface
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'wechat';
|
||||
}
|
||||
|
||||
public function send(array $target, array $notice): bool
|
||||
{
|
||||
/*
|
||||
* TODO 下游对接指引(公众号模板消息):
|
||||
* - 实现 access_token 管理:用 sysconfig('notify','notify_wechat_appid'/'notify_wechat_secret')
|
||||
* 调用微信 token 接口获取凭证,缓存至过期前(token 有效期 7200s,需集中缓存防刷);
|
||||
* - 用 guzzlehttp/guzzle 调用模板消息接口(POST /cgi-bin/message/template/send),
|
||||
* target_addr 为接收用户 openid,notice(title/content)映射模板字段;
|
||||
* - 微信返回 errcode 非 0 时抛出 RuntimeException(携带 errcode/errmsg),
|
||||
* 交由 timer 消费者退避重试。
|
||||
*/
|
||||
|
||||
// 骨架驱动模拟发送:开关状态仅记入日志(关闭也恒成功),不拦截投递、不真实外呼
|
||||
$enabled = (int) sysconfig('notify', 'notify_wechat_enable');
|
||||
Log::info('notice wechat channel skeleton simulated send', [
|
||||
'channel' => self::name(),
|
||||
'enabled' => $enabled,
|
||||
'notice_id' => $notice['id'] ?? 0,
|
||||
'target_addr' => $target['target_addr'] ?? '',
|
||||
'title' => $notice['title'] ?? '',
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user