diff --git a/docs/Email.md b/docs/Email.md new file mode 100644 index 0000000..4c86d1e --- /dev/null +++ b/docs/Email.md @@ -0,0 +1,76 @@ +# Email | 邮件 +提供一套通用 Service 用于定制需求,和一套现成的事件/监听器用于异步发送。 + +## 服务类 +[\Singularity\HDK\Core\Service\EmailService](../src/Service/EmailService.php) +* 发送纯文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendText()` +* 发送 Html 富文本邮件的 `\Singularity\HDK\Core\Service\EmailService::sendHtml()` + +## 事件机制 +> 这种方式无法监听邮件发送的结果! +> +> 如果你需要根据邮件发送的结果进行处理, +> 请使用服务类进行自定义 + +更好的方法可能是在事件发生后触发异步队列, +但如果将异步队列封装在本包中, +相当于强制引用本库的项目使用异步队列机制, +这是我们不愿意看到的。 +所以这里的监听器只是集成了同步发送的操作。 + +如果你想要定义更好的设计, +你可以直接使用提供的 Service 类进行定制。 +> 或许这里还有优化空间😄 + +### 事件 +[\Singularity\HDK\Core\Events\EmailWillSent](../src/Events/EmailWillSent.php) +### 监听器 +[\Singularity\HDK\Core\Listener\EmailWillSentListener](../src/Listener/EmailWillSentListener.php) +### 使用方法 +监听器已经通过 [ConfigProvider](../src/ConfigProvider.php) 机制注册, +所以你只需要在要发送邮件的地方调度这个事件即可。 +> 这也就意味着,你无法调用这个事件,但不触发这个监听器。 +> 如果你有这样做的必要,请创建一个自己的事件。 +> 这也是为了保证功能的完整性和原子性。 + +具体使用方法,如下代码所示: + +```php +save(); + + // ↓ + // 这里 dispatch(object $event) 会逐个运行监听该事件的监听器 + $this->eventDispatcher->dispatch(new EmailWillSent( + target: $user->mobile, + subject: '注册成功通知', + type: 'html', // 默认 text 可以不传 + content: <<恭喜你,注册成功! +HTML, + )); + // ↑ + + return $result; + } +} + +``` \ No newline at end of file diff --git a/docs/Sms.md b/docs/Sms.md new file mode 100644 index 0000000..100e7d7 --- /dev/null +++ b/docs/Sms.md @@ -0,0 +1,75 @@ +# SMS | 短信 +提供一套通用 Service 用于定制需求,和一套现成的事件/监听器用于异步发送。 + +## 服务类 +[\Singularity\HDK\Core\Service\SmsService](../src/Service/SmsService.php) +* 发送国内短信的 `\Singularity\HDK\Core\Service\SmsService::sendSmsCountryside()` + +## 事件机制 +> 这种方式无法监听短信发送的结果! +> +> 如果你需要根据短信发送的结果进行处理, +> 请使用服务类进行自定义 + +更好的方法可能是在事件发生后触发异步队列, +但如果将异步队列封装在本包中, +相当于强制引用本库的项目使用异步队列机制, +这是我们不愿意看到的。 +所以这里的监听器只是集成了同步发送的操作。 + +如果你想要定义更好的设计, +你可以直接使用提供的 Service 类进行定制。 +> 或许这里还有优化空间😄 + +### 事件 +[\Singularity\HDK\Core\Events\SmsWillSent](../src/Events/SmsWillSent.php) +### 监听器 +[\Singularity\HDK\Core\Listener\SmsWillSentListener](../src/Listener/SmsWillSentListener.php) +### 使用方法 +监听器已经通过 [ConfigProvider](../src/ConfigProvider.php) 机制注册, +所以你只需要在要发送短信的地方调度这个事件即可。 +> 这也就意味着,你无法调用这个事件,但不触发这个监听器。 +> 如果你有这样做的必要,请创建一个自己的事件。 +> 这也是为了保证功能的完整性和原子性。 + +具体使用方法,如下代码所示: + +```php +save(); + + // ↓ + // 这里 dispatch(object $event) 会逐个运行监听该事件的监听器 + $this->eventDispatcher->dispatch(new EmailWillSent( + target: $user->mobile, + subject: '注册成功通知', + type: 'html', // 默认 text 可以不传 + content: <<恭喜你,注册成功! +HTML, + )); + // ↑ + + return $result; + } +} + +``` \ No newline at end of file diff --git a/phpstan.dist.neon b/phpstan.dist.neon index e76ecf2..5fd3d60 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -6,4 +6,6 @@ parameters: - tests ignoreErrors: - '#Constant BASE_PATH not found#' - - '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read.#' \ No newline at end of file + - '#Property [a-zA-Z0-9\\_]+::\$[a-zA-Z0-9]+ is never written, only read.#' + - '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9_]+\(\) has parameter \$[a-zA-Z0-9_]+ with no value type specified in iterable type array.#' + - '#Method [a-zA-Z0-9\\_]+::[a-zA-Z0-9_]+\(\) has parameter \$[a-zA-Z0-9_]+ with no type specified.#' \ No newline at end of file diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index 6d71981..4cc500a 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -14,6 +14,8 @@ namespace Singularity\HDK\Core; use Hyperf\Contract\StdoutLoggerInterface; use Hyperf\Framework\Logger\StdoutLogger; +use Singularity\HDK\Core\Listener\EmailWillSentListener; +use Singularity\HDK\Core\Listener\SmsWillSentListener; class ConfigProvider { @@ -38,7 +40,10 @@ class ConfigProvider 'commands' => [ ], // 与 commands 类似 - 'listeners' => [], + 'listeners' => [ + EmailWillSentListener::class, + SmsWillSentListener::class + ], // 组件默认配置文件,即执行命令后会把 source 的对应的文件复制为 destination 对应的的文件 'publish' => [ [ diff --git a/src/Events/EmailWillSent.php b/src/Events/EmailWillSent.php new file mode 100644 index 0000000..7afb21a --- /dev/null +++ b/src/Events/EmailWillSent.php @@ -0,0 +1,69 @@ + + * Powered by PhpStorm + * Created on 2023/1/16 + */ + +namespace Singularity\HDK\Core\Events; + +/** + * Singularity\HDK\Core\Events\EmailWillSent@HDK-Core + * + * @author 李东云 + * Powered by PhpStorm + * Created on 2023/1/16 + * + * @link ../../docs/Email.md + */ +class EmailWillSent +{ + /** + * @var string|string[] $target + */ + public $target; + + /** + * @var non-empty-string $subject + */ + public string $subject; + + + /** + * @var non-empty-string $content + */ + public string $content; + + /** + * @var string[] $cc + */ + public array $cc = []; + + /** + * @var string 'text'|'html' $type + */ + public string $type = 'text'; + + + public function __construct( + /** + * @phpstan-param string|string[] $target + */ + $target, + string $subject, + string $content, + /** + * @var non-empty-string[] $cc + */ + array $cc = [], + string $type = 'text' + ) { + $this->type = $type; + $this->cc = $cc; + $this->content = $content; + $this->subject = $subject; + $this->target = $target; + } +} diff --git a/src/Events/SmsWillSent.php b/src/Events/SmsWillSent.php new file mode 100644 index 0000000..6929ffc --- /dev/null +++ b/src/Events/SmsWillSent.php @@ -0,0 +1,75 @@ + + * Powered by PhpStorm + * Created on 2023/1/16 + */ + +namespace Singularity\HDK\Core\Events; + +/** + * Singularity\HDK\Core\Events\SmsWillSent@HDK-Core + * + * @author 李东云 + * Powered by PhpStorm + * Created on 2023/1/16 + * + * @link ../../docs/SMS.md + */ +class SmsWillSent +{ + /** + * @var non-empty-string $phone 目标手机号 + * @example +8613700000001 + * @example 8613700000001 + * @example 13700000001 + */ + public string $phone; + + /** + * @var array|null $templateParam 模板变量 + */ + public ?array $templateParam = null; + + /** + * @var literal-string|null $templateCode 模板代码 + */ + public ?string $templateCode = null; + + /** + * @var literal-string|null $signName 模板签名 + */ + public ?string $signName = null; + + public function __construct( + /** + * @var non-empty-string $phone 目标手机号 + * @example +8613700000001 + * @example 8613700000001 + * @example 13700000001 + */ + string $phone, + + /** + * @var array|null $templateParam 模板变量 + */ + ?array $templateParam = null, + + /** + * @var literal-string|null $templateCode 模板代码 + */ + ?string $templateCode = null, + + /** + * @var literal-string|null $signName 模板签名 + */ + ?string $signName = null + ) { + $this->signName = $signName; + $this->templateCode = $templateCode; + $this->templateParam = $templateParam; + $this->phone = $phone; + } +} diff --git a/src/Listener/AbstractListener.php b/src/Listener/AbstractListener.php new file mode 100644 index 0000000..4eb8107 --- /dev/null +++ b/src/Listener/AbstractListener.php @@ -0,0 +1,30 @@ + + * Powered by PhpStorm + * Created on 2023/1/16 + */ + +namespace Singularity\HDK\Core\Listener; + +use Hyperf\Contract\ContainerInterface; +use Hyperf\Contract\StdoutLoggerInterface; +use Hyperf\Di\Annotation\Inject; +use Hyperf\Event\Contract\ListenerInterface; + +abstract class AbstractListener implements ListenerInterface +{ + /** + * @var ContainerInterface + * @Inject() + */ + protected ContainerInterface $container; + + /** + * @var StdoutLoggerInterface + * @Inject() + */ + protected StdoutLoggerInterface $stdoutLogger; +} diff --git a/src/Listener/EmailWillSentListener.php b/src/Listener/EmailWillSentListener.php new file mode 100644 index 0000000..7976cb0 --- /dev/null +++ b/src/Listener/EmailWillSentListener.php @@ -0,0 +1,90 @@ + + * Powered by PhpStorm + * Created on 2023/1/16 + */ + +namespace Singularity\HDK\Core\Listener; + +use Hyperf\Contract\ContainerInterface; +use Hyperf\Contract\StdoutLoggerInterface; +use Hyperf\Di\Annotation\Inject; +use Hyperf\Event\Contract\ListenerInterface; +use JetBrains\PhpStorm\NoReturn; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Singularity\HDK\Core\Constants\CommonErrorCode; +use Singularity\HDK\Core\Events\EmailWillSent; +use Singularity\HDK\Core\Service\EmailService; +use Symfony\Component\Mailer\Exception\TransportExceptionInterface; + +/** + * Singularity\HDK\Core\Listener\EmailWillSentListener@HDK-Core + * + * @author 李东云 + * Powered by PhpStorm + * Created on 2023/1/16 + * + * @link ../../docs/Email.md + */ +class EmailWillSentListener extends AbstractListener +{ + /** + * @inheritDoc + */ + public function listen(): array + { + return [ + EmailWillSent::class, + ]; + } + + /** + * @param EmailWillSent $event + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function process(object $event): void + { + $stdoutLogger = $this->container->get(StdoutLoggerInterface::class); + $emailService = $this->container->get(EmailService::class); + try { + $event->type === 'html' + ? $emailService->sendHtml( + $event->target, + $event->subject, + $event->content, + $event->cc + ) + : $emailService->sendText( + $event->target, + $event->subject, + $event->content, + $event->cc + ); + + $stdoutLogger->info('邮件发送成功!'); + $stdoutLogger->info("To: $event->target"); + $stdoutLogger->info("Subject: $event->subject"); + $stdoutLogger->debug('Content: '); + $stdoutLogger->debug($event->content); + } catch (TransportExceptionInterface $e) { + $code = CommonErrorCode::SERVER_MESSAGE_EMAIL_ERROR; + $msg = $e->getMessage(); + if (strpos($msg, '500 Error: bad syntax')) { + $code = CommonErrorCode::SERVER_MESSAGE_EMAIL_NOT_FOUND; + $msg = CommonErrorCode::getMessage($code); + } + $stdoutLogger->alert('邮件发送失败!'); + $stdoutLogger->error( + << + * Powered by PhpStorm + * Created on 2023/1/16 + */ + +namespace Singularity\HDK\Core\Listener; + +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; +use Singularity\HDK\Core\Events\SmsWillSent; +use Singularity\HDK\Core\Service\SmsService; +use Throwable; + +/** + * Singularity\HDK\Core\Listener\SmsWillSentListener@HDK-Core + * + * @author 李东云 + * Powered by PhpStorm + * Created on 2023/1/16 + */ +class SmsWillSentListener extends AbstractListener +{ + /** + * @inheritDoc + */ + public function listen(): array + { + return [ + SmsWillSent::class, + ]; + } + + /** + * @param SmsWillSent $event + * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function process(object $event): void + { + $smsService = $this->container->get(SmsService::class); + try { + $smsService->sendSmsCountryside( + $event->phone, + $event->templateCode, + $event->templateParam, + $event->signName + ); + } catch (Throwable $throwable) { + $code = $throwable->getCode(); + $message = $throwable->getMessage(); + $this->stdoutLogger->alert('短信发送失败!'); + $this->stdoutLogger->error(sprintf("[%u] %s", $code, $message)); + } + } +}